Beginner

HTML & CSS Fundamentals

Both divs share the class tutorial (so they can be styled as a set) but each has its own unique id (so either can be targeted individually).

Table of Contents

This course teaches web development from a completely blank page all the way up to building a small, real, interactive website. Over the course of the material, you will build up an understanding of how a web page is structured with HTML, given a look and feel with CSS, and made interactive with JavaScript. The major topics covered include the meaning and purpose of HTML elements, how a page gets its appearance from style sheets, the basics of computer programming, and how to write code that makes a page interactive.

By the end of the course, you will have built a small responsive website — Bethany’s Pie Shop — from scratch, including a working order-total calculator, and you should feel ready to move on to front-end frameworks such as React, Angular, or Vue; back-end technologies such as Node.js; and more advanced CSS techniques.

mindmap
  root((Web Page))
    HTML
      Structure
      Elements and tags
      Semantic meaning
    CSS
      Style and look and feel
      Selectors and rulesets
      Layout (Box model, Flexbox)
      Responsiveness (media queries)
    JavaScript
      Behavior and interactivity
      Variables, functions, objects
      DOM manipulation
      Events

Module 1: Getting Started with Web Development

The Final Project: Bethany’s Pie Shop

Throughout this course, you build a website from the ground up for a fictional client, Bethany, who runs a local business selling pastries, pies, and other baked treats. She wants to take her pie shop online, and the finished website acts as the running example for every concept introduced.

The finished home page welcomes customers to the store, reflects Bethany’s brand identity, and includes a bit of her story. Further down the page there is an order calculator where customers can get an order total for the pies they want to buy just by typing quantities into input boxes.

The site is also built to be responsive: it looks good on both desktop and mobile devices. Opening the browser’s developer tools and switching to a mobile device emulation view reveals a different layout — the icon logo disappears and the hero picture is cropped differently — all achieved by building the page to adapt to both mobile and desktop contexts.

The topics needed to build this page, in order, are:

  1. HTML — the structure of the content.
  2. CSS — the styling, appearance, and layout, including responsive behavior.
  3. JavaScript — the interactivity (the order calculator logic).

Writing Your First HTML Element

To get a feel for HTML immediately, you can open a browser and go to an online HTML/CSS/JS sandbox (such as codepen.io/pen) and expand the HTML pane. Typing the following is your first HTML element:

<h1></h1>

The h stands for header, meaning this is the primary, or first, header on the page — essentially the title of the page. Adding text between the opening and closing tags produces a complete element:

<h1>Hello World</h1>

“Hello World” is a traditional placeholder message used since an old programming book from the 1970s to demonstrate the simplest possible program in a given language — you will see it used often as you continue learning to program.

This tiny example is not yet a full web page, but it demonstrates the core idea: web pages are made up of elements that, when stacked together in just the right way, form the foundation of the web.

Setting Up Your Code Editor

Many code editors are available, most of them free with rich features and large communities. Visual Studio Code (code.visualstudio.com) is the recommended editor for this course. It is free, cross-platform (Windows, macOS, Linux), and supports extensions — additional functionality that anyone can build and share.

A particularly useful extension for front-end development is Live Server, which serves your project’s files to the browser the same way a real web server would — without you needing to write any server-side code — and automatically refreshes the browser whenever you save a change.

Steps to set it up:

  1. Install VS Code from code.visualstudio.com.
  2. Open the Extensions view (the icon that looks like a box being snapped into another box) from the left icon bar.
  3. Search for Live Server and click Install.
  4. Reload the VS Code window (Ctrl+Shift+P / Cmd+Shift+P, then type “Reload Window” and press Enter).
  5. A Go Live button appears in the bottom status bar. Clicking it launches the current page in the browser through the extension.

VS Code also ships with Emmet, a set of shortcuts that speed up writing HTML. For example, typing html:5 and pressing Tab scaffolds a complete HTML5 document stub. Typing Hello World inside the <body> and saving, then right-clicking the file and choosing Open with Live Server, launches the page in the browser. From that point on, every time you save the file, Live Server automatically updates the browser with the latest changes.

Getting the Sample Project Files

The course’s example code is hosted on GitHub using two branches:

BranchPurpose
mainThe finished version of the site — what your code should resemble once the course is complete.
startThe starting files you need to begin the course, without the code you are about to write.

To obtain the starter files without needing deep GitHub knowledge, you can download a zip archive containing them, extract it, and open the resulting folder in Visual Studio Code (File > Open Folder…). The starter folder already contains the images for the website as well as placeholder HTML, script, and style files that you build up throughout the course.

Module 2: Building Your First Web Page

What Is HTML?

HTML stands for HyperText Markup Language. Breaking that down piece by piece:

  • HyperText refers to the fact that documents on the web are linked together through hyperlinks. Ignoring the “hyper” prefix for a moment, this means text documents are connected together by links — the foundation of the web. The prefix “hyper” is used in the sense of making something bigger (as in “hyperspace”), not in the sense of “hyperactive.” As you navigate from one site to the next, they are all tied together through a web of links, which is where the term “the web” comes from.
  • Markup Language refers to the code you write to create these documents and establish the links between them.

The building blocks of a markup language are called HTML elements.

What Is an HTML Element?

An element is a building block of a web page — much like a single brick is a building block of a structure built from many bricks. HTML elements are combined together to build the structure of an entire web page.

You can inspect the underlying HTML of any page in a couple of ways:

  • Right-click → View Page Source shows the raw HTML of the page. This works fine for a very small page, but on a real, large website the output becomes difficult to read.
  • Right-click → Inspect opens the Elements pane of the browser’s developer tools. This is a much better way to explore markup on real-world pages: elements are collapsible, hovering over an element highlights it on the rendered page, and you can navigate the structure of even a very large page.

So, every HTML page is made up of a series of HTML elements that build on one another, just like a series of bricks set together just right make up a house.

Elements: Tags and Attributes

Each HTML element is made up of at least two parts:

  • Tag — identifies what the element is and the role it plays on the page (for example html, head, body, h1, p, img).
  • Attribute(s) — additional information associated with the element. For example, an img element has src and title attributes.

An analogy: if you described yourself to someone, you might say “I’m a person” (the element’s tag), and then add details like “my name is Craig, I have brown eyes and brown hair” (the element’s attributes). In the same way, an image element has the img tag, and its src and title values are its attributes. The src attribute tells the browser where to find an image file on the server; the title attribute gives a title, or brief description, of the element.

So, HTML pages are made up of a series of building-block elements. Each element is identified by its tag, and each element can have zero, one, or many attributes. Two attributes you will see repeatedly throughout this course are:

AttributePurpose
idStands for identifier — gives a unique name to a single element on the page.
classIdentifies an element and can also be used to change how it looks via a style sheet. A class can be applied to many elements at once.

A Tour of Common HTML Elements

The MDN Web Docs HTML elements reference is the definitive place to learn about every available HTML element and web API. Broadly, elements are grouped into categories such as:

CategoryExamples / Purpose
Document structurehtml, head, body
Content sectioningElements that divide a page into logical regions
Text contentElements for blocks of text
Inline text semanticsElements describing meaning within a run of text
Image and multimediaPictures, audio, video
Embedded contentEmbedding external resources
ScriptingElements that host or reference JavaScript
Demarcating editsElements that show insertions/deletions
Table contentElements for tabular data
FormsElements for collecting user data
Interactive elementsElements users can directly interact with
Web componentsElements enabling custom, reusable components
Obsolete/deprecatedElements no longer supported on the web

You do not need to memorize every element up front — the best approach is to spend time becoming familiar with elements and learning what kind of content they are most appropriate for, one element at a time.

Using Common Elements with Emmet

Emmet is a tool built into Visual Studio Code that speeds up writing HTML through abbreviations: type a short string of characters and press Tab to expand it into full markup.

Building a basic page with Emmet:

<!-- typing "html:5" + Tab scaffolds this stub -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

Inside <body>, typing h1 + Tab creates an <h1> element with the cursor placed between the tags, ready for a page title:

<h1>My Page Title</h1>

Adding a paragraph (p + Tab):

<p>Some introductory paragraph text.</p>

Adding a link (a + Tab) automatically includes the href attribute, which stands for hyperlink reference and points the link to a web address:

<a href="https://www.example.com">Visit our site</a>

Adding a table with Emmet’s table abbreviation scaffolds the root <table> element, and further abbreviations build out the header and body:

<table>
    <thead>
        <tr>
            <th>Name</th>
            <th>Twitter</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>Craig Shoemaker</td>
            <td>@craigshoemaker</td>
        </tr>
        <tr>
            <td>John Papa</td>
            <td>@john_papa</td>
        </tr>
    </tbody>
</table>

Rendered in the browser, this produces a very plain-looking page: a title, a paragraph, and an unstyled table. The structure — the HTML — is complete; styling comes next.

The Document Object Model (DOM)

The browser represents a loaded web page in memory through the Document Object Model (DOM). Every web page is a single document inside the browser, and that document is represented by an in-memory object simply called document.

Opening the developer tools’ Console tab and typing document and pressing Enter returns a reference to the in-memory document. Expanding it reveals essentially everything visible in the Elements tab — the entire HTML structure of the page is available through document.

An object is best understood as an approximation of something in the real world. Just as thinking of “a house” conjures walls, a roof, windows, and a door — while glossing over the plumbing, wiring, and paint — programming objects are abstractions: placeholders for things whose full detail is not the immediate concern. In the browser, each element on the page is represented by an object, and together these objects form the DOM.

The word Model in DOM alludes to the fact that it is a representation of a structure, similar to how a model plane represents a real plane. The browser uses this tree of objects (the DOM) to understand and interface with the page.

flowchart TD
    A[document] --> B[html]
    B --> C[head]
    B --> D[body]
    C --> C1[title]
    C --> C2[meta tags]
    D --> D1[h1]
    D --> D2[p]
    D --> D3[table]
    D3 --> D3a[thead]
    D3 --> D3b[tbody]

A useful DevTools shortcut: whenever you select an element in the Elements tab, the browser creates a temporary reference to it accessible in the console as $0. Selecting a different element and typing $0 again returns a reference to the newly selected element — a quick way to inspect whatever you have currently selected in the Elements panel.

How Search Engines Read Markup

Search engines interpret a web page differently than a human looking at it in the browser. The key concept here is semantic markup — not a special set of HTML elements, but rather how you use elements together to convey the relative significance of content.

Consider this non-semantic fragment:

Introduction
Once upon a time there was a beautiful princess who lived happily ever after.

As far as the HTML is concerned, there is no way to tell that “Introduction” is a title with different significance than the following text — both are just plain text with no distinguishing markup.

Rewriting it with semantic elements:

<h1>Introduction</h1>
<p>Once upon a time there was a beautiful princess who lived happily ever after.</p>

Now a computer can tell that <h1> is a page header — not just any title, but the primary and most important one on the page. Search engines use many criteria to determine rankings, but one clear signal is which tag wraps a piece of content:

ElementSignal sent to a search engine
<h1>Primary title of the page — most important content
<h2>Secondary heading — still important, but subordinate to <h1>
<main>The main, most important content of the page
<aside>Extra/supplementary content, not core to the page’s message

Writing semantic markup means being familiar with the intended purpose of each tag and using tags according to their documented meaning, rather than arbitrarily.

Validating Your HTML

To check whether HTML follows the specification correctly, you can submit it to a validation service. The W3C (the organization responsible for the HTML specification) provides a validator at validator.w3.org.

You can validate code by supplying a URL, uploading a file, or pasting code directly (“direct input”). After clicking Check, the validator reports issues — for example, a missing lang attribute on the <html> element, or a missing <!DOCTYPE html> declaration at the very start of the page.

As you write more HTML, periodically validating it helps ensure your markup follows the rules of the specification correctly.

Anatomy of a Web Page: HTML, CSS, and JavaScript

A finished web page — the user interface — is the product of three technologies working together. An analogy to a person helps clarify the roles:

Aspect of a personWeb technologyRole
Skeletal system (bone structure)HTMLStructure of the page/content
Clothes (physical appearance)CSS (Cascading Style Sheets)Style, look, and feel
Brain (thought, logic, reasoning)JavaScriptInteractivity, programming logic, business rules
flowchart LR
    HTML["HTML — Structure<br/>(skeleton)"] --> Page[Rendered Web Page]
    CSS["CSS — Style<br/>(clothes)"] --> Page
    JS["JavaScript — Behavior<br/>(brain)"] --> Page

HTML alone (as seen in the earlier bare examples) is not much to look at — just information thrown on the page. CSS provides the style and layout. JavaScript brings the interactivity. Together, the three form a fully functioning website.

Module 3: Adding Style with CSS

CSS Style Basics

Cascading Style Sheets (CSS) let you describe how you want elements to appear on the page — fonts, colors, sizes, layouts, and much more. Style rules can be added in a few different locations (covered later in this module). A simple starting example, adding a <style> block to the <head> of a page:

<head>
  <style>
    div {
      background-color: #333;
      color: #fff;
    }
  </style>
</head>
<body>
  <div>Hello</div>
</body>

Each style rule is formatted as a property name, a colon, and a value. CSS accepts color information in a few different formats; the example above uses a hex code. Visual Studio Code shows a small color preview/picker when you hover over a recognized color value, and lets you pick any color visually.

Anatomy of a CSS Ruleset

A complete CSS rule, taken as a whole, is known as a ruleset. Consider the following example:

.container p:first-child::first-letter {
  font-size: 2em;
}
PartExampleMeaning
Selector.container pLocates the element(s) the browser should target
Pseudo-class:first-childSelects an element based on state/relationship — here, the very first child
Pseudo-element::first-letterSelects a specific character/portion of an element — here, its first letter
Declaration block{ ... }The curly braces bound the rules applied to the matched elements
Declaration (property + value)font-size: 2em;A single rule; made of a property and a value (or keyword)

In plain English, the selector above says: “Find an element with the class container, locate the paragraph inside it, find its first child element, and apply the style only to the very first letter of that element.”

Cascading Rules

CSS is cascading — multiple rules can apply to the same element, and specific precedence rules decide which one wins. The simplest case: when two rules with equal specificity target the same selector, the rule that appears last in the source wins.

h1 {
  color: blue;
}

h1 {
  color: gray; /* this one wins — it comes later */
}

Specificity

Specificity determines which rule wins when rules of different selector types conflict. A class-based rule is more specific than an element (tag) rule and therefore wins, regardless of source order:

.heading {
  color: blue; /* wins, because .heading is more specific than h1 */
}

h1 {
  color: gray;
}
<h1 class="heading">Title</h1>

The reasoning: an element’s tag broadly describes what it is, but a class adds more specific information about that particular instance. A general rule of thumb ordering, from least to most specific: tag selector < class selector < ID selector < inline style attribute.

Inheritance

Some CSS properties are inherited by child elements from their parent; others are not.

body {
  background-color: lightgray; /* inherited by children unless overridden */
}

h1 {
  background-color: white; /* overrides the inherited value for h1 only */
}

Dimensional properties such as width are not inherited:

body {
  background-color: lightgray;
}

main {
  background-color: gray;
  width: 50%;
}

div {
  background-color: darkgray; /* takes up 100% of its container (main), not 50% */
}

Since width does not inherit, the div fills 100% of its parent main element unless it is given its own explicit width. Inheritance is powerful because it saves you from restating the same rules over and over for every nested element.

Where to Place Style Rules

CSS rules can be placed in three different locations, and where you place them affects how they cascade:

LocationExampleNotes
Inline style attribute<h1 style="color: blue;">Highest precedence; least flexible — generally discouraged except in special cases
<style> element in <head><style>h1 { color: blue; }</style>Applies to the current document only
External stylesheet file (.css)<link rel="stylesheet" href="styles.css">Recommended: enables browser caching and easier maintenance across multiple pages

Linking an external stylesheet:

<head>
  <link rel="stylesheet" href="styles.css">
</head>
/* styles.css */
h1 {
  color: blue;
}

Using an external CSS file is important for caching: the browser downloads the stylesheet once and each page in the site can reference the same cached file, making the site faster overall, while also making the styles easier to maintain.

If styles for the same selector and property exist in more than one of these locations at once, the cascade resolves as follows (from lowest to highest precedence):

flowchart LR
    A["External stylesheet<br/>(styles.css)"] --> B["Style element<br/>(&lt;style&gt; in &lt;head&gt;)"]
    B --> C["Inline style attribute<br/>(style=&quot;...&quot;)"]
    C --> D[Wins: most specific in scope]

Introduction to Selectors

Selectors locate elements on the page so CSS knows which ones to style. The main categories are tag, class, ID, and attribute selectors. Example markup used to demonstrate each type:

<h1>Page Title</h1>
<p>Some intro text.</p>
<div id="tutorial-1" class="tutorial">
  <h2>Subheading 1</h2>
  <img src="https://via.placeholder.com/150">
  <p>Label text</p>
</div>
<div id="tutorial-2" class="tutorial">
  <h2>Subheading 2</h2>
  <img src="https://via.placeholder.com/150">
  <p>Label text</p>
</div>

Both divs share the class tutorial (so they can be styled as a set) but each has its own unique id (so either can be targeted individually).

Tag Selectors

A tag selector targets every element with a given tag name:

img {
  border: 1px solid gray;
}

This applies the border to every <img> element on the page.

Class Selectors

A class selector begins with a dot (.) and targets every element carrying that class name:

.tutorial {
  border: 1px solid black;
}

Class selectors are valuable because the same class (label) can be applied to many different elements, letting you style a whole category of elements — e.g., “all tutorial containers” — consistently.

ID Selectors

An ID selector begins with a hash/pound sign (#) and targets the single element carrying that unique id:

#tutorial-1 {
  background-color: #eee;
}

You typically use id to uniquely identify one specific element, such as a container that needs distinct styling or an element that collects data from the user.

Attribute Selectors

An attribute selector targets elements based on the presence or value of an attribute, using square brackets:

img[src="https://via.placeholder.com/150"] {
  border: 3px solid blue;
}

This selects only <img> elements whose src attribute equals the given value — giving very precise targeting when needed.

Multiple (List) Selectors

A list selector applies the same rule to more than one selector at once by separating them with commas:

h1, h2 {
  color: darkslateblue;
}

.tutorial, h1 {
  border: 1px solid black;
}

The second example mixes a class selector and a tag selector in the same list, styling both the tutorial containers and the top-level heading with a border.

Combinators

A child combinator (>) targets elements that are direct children of another selected element:

.tutorial > p {
  font-size: 1.2em;
}

This targets only <p> elements that are direct children of an element with the tutorial class — not paragraphs nested more deeply, and not paragraphs outside a .tutorial container.

Building Styles in Real Time with DevTools

Rather than guessing at a selector, saving a file, and checking the browser repeatedly, the browser’s own developer tools let you experiment live: right-click an element and choose Inspect, then use the Styles panel next to the Elements tab to add and toggle CSS rules and see the effect immediately, without editing any file.

A typical experimental sequence turning a plain <div> into a styled button:

.button {
  border: 1px solid #333;
  padding: 10px 16px;
  background-color: #ccc;
  width: 40px;
  text-align: center;
  font-family: Arial, Helvetica, sans-serif;
  cursor: pointer;
}

Once the rule looks right in the live Styles panel, you copy it back into your actual stylesheet (creating a .button class rule) and apply the class to the element in HTML — that change persists across page reloads, whereas edits made only in DevTools are lost on refresh.

Module 4: Working with CSS Layout and Responsiveness

The Box Model and Box Sizing

Every element on a page can be thought of as a rectangle — a box. When several boxes sit next to each other, the browser must decide how to calculate the space each one occupies. Key terms:

TermMeaning
Width / HeightThe element’s own content dimensions
BorderThe visible outline drawn around the box; can vary in thickness
MarginInvisible space outside the border, keeping other elements away from this box
PaddingInvisible space inside the border, between the border and the box’s content
flowchart TD
    subgraph Margin[Margin]
        direction TB
        subgraph Border[Border]
            direction TB
            subgraph Padding[Padding]
                direction TB
                Content[Content: width x height]
            end
        end
    end

Given an element declared with width: 150px, a 10px border, and 10px of padding, there are two possible interpretations of “how wide is this box, really?”:

Box modelRendered width calculationTotal rendered width
Classic (content-box)width + border + padding are added on top150 + 10 + 10 + 10 + 10 = 170px
border-boxBorder and padding are rendered inside the declared width150px (border/padding eat into the content area)

Both models are supported by browsers; box-sizing: border-box is generally the recommended approach because the math stays predictable while designing a layout. A common pattern applies it globally:

html {
  box-sizing: border-box;
}

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

With this in place, every element (and its ::before/::after pseudo-elements) inherits the border-box sizing strategy, so declared widths and heights already account for border and padding.

Building Layouts with Flexbox

Flexbox is one of the easiest ways to build multi-column layouts. Starting from a container with a class and two child columns:

<div class="columns">
  <div>Column 1</div>
  <div>Column 2</div>
</div>
.columns {
  border: 1px solid #333;
  width: 100%;
  display: flex;
}

.columns > div {
  border: 1px solid #999;
  flex: 1;
}

Setting display: flex on the container immediately arranges the child divs side by side. Setting flex: 1 on each child tells the browser to evenly distribute all available space among the children, giving each one an equal share (one “portion” of the total space). Adding a third <div> automatically results in three equal-width columns, because the available space is now split three ways.

To give one column more room than the others, target it specifically using the :nth-of-type pseudo-class and give it a larger flex value:

.columns > div:nth-of-type(2) {
  flex: 2;
}

This makes the second column take up two proportional units of space, while the first and third columns each take one unit — so, out of 4 total units, column 2 gets half the available width and columns 1 and 3 split the rest.

Flex propertyPurpose
display: flexTurns on Flexbox layout for the container’s direct children
flex: <n>Shorthand controlling how much proportional space a flex item claims relative to its siblings
flex-directionControls the main axis direction (e.g. row, row-reverse)
flex-basisThe starting size of a flex item before growing/shrinking
flex-growHow much an item grows to fill extra space
flex-shrinkHow much an item shrinks when space is constrained

The Responsive Viewport

Before writing responsive CSS rules, a special metadata tag is required so mobile devices interpret the page’s width correctly:

<meta name="viewport" content="width=device-width, initial-scale=1.0">
  • width=device-width tells the browser to render the viewport at the actual width of the device, rather than a desktop-sized default.
  • initial-scale=1.0 sets the initial zoom level to 100% — neither zoomed in nor zoomed out.

Without this tag, mobile browsers commonly render the page as if it were a wide desktop page and then shrink it to fit, defeating the purpose of a responsive layout.

Responsive Design with Media Queries

Media queries let you target CSS rules at specific device types or viewport sizes, enabling one style sheet to serve very different layouts to desktop and mobile.

Best practice is to design mobile-first: write the base styles for small screens, then use a media query to add or override rules for larger screens. Given three placeholder blocks intended for different contexts:

<div class="mobile">Mobile content</div>
<div class="desktop">Desktop content</div>
<div class="print">Print-only content</div>

Hide the desktop- and print-only elements by default (mobile-first baseline):

.desktop, .print {
  display: none;
}

A print media query reverses the visibility for printed output:

@media print {
  .mobile, .desktop {
    display: none;
  }
  .print {
    display: block;
  }
}

A screen media query targets the desktop viewport, activating once the browser is at least 768px wide:

@media screen and (min-width: 768px) {
  .mobile {
    display: none;
  }
  .desktop {
    display: block;
  }
}

Below 768px the mobile elements show; at 768px and above, the desktop elements take over instead. The browser’s developer tools include a device toolbar (toggle-able from an icon in DevTools) that simulates a range of real devices, orientations, and zoom levels, which is far more reliable for testing media queries than manually resizing the browser window.

flowchart TD
    A[Viewport width] -->|"< 768px"| B[Mobile styles: .mobile visible]
    A -->|">= 768px (screen)"| C[Desktop styles: .desktop visible]
    A -->|"@media print"| D[Print styles: .print visible]

Checking Browser Support

To confirm whether a given CSS feature is safe to use across browsers, the reference site caniuse.com shows a compatibility table for the feature, developer notes, and a “Known issues” section covering edge cases.

CSS feature exampleTypical support level
FlexboxBroad support across virtually all modern browsers
Hanging punctuationPoor support — not safe to rely on in production

Most modern layout techniques used in this course (Flexbox, media queries, custom properties) are well supported, but it is good practice to check caniuse.com whenever you are unsure about a newer or more obscure CSS feature.

Module 5: Introduction to JavaScript

JavaScript is the language of the web: it runs in the browser (and can also run on servers). This module focuses on the code you write to make a web page interactive in the browser. A computer’s processor really only understands 1s and 0s; programming languages like JavaScript exist to translate human-readable instructions into something the machine can execute. An interpreter is the program that reads JavaScript line by line and carries out the instructions.

All of the experiments below can be tried directly in a browser’s Console tab (open with about:blank in the address bar, then open DevTools). Ctrl/Cmd+L clears the console.

Strings

Typing a bare word like hello into the console produces a ReferenceError: hello is not defined, because the interpreter thinks you are referring to a variable that does not exist. Wrapping the value in quotation marks tells the interpreter it is a string — a series of characters:

"hello"
// "hello"

"hello".length
// 5

Strings represent text, including whitespace such as spaces and line breaks, strung together as a single value.

Numbers

The console also understands numbers natively, and can evaluate arithmetic expressions directly:

1 + 1
// 2

6 * 6 / (2 * 3)
// 6

An expression is a line of code that, when evaluated, produces a result. This concept extends far beyond arithmetic — expressions appear throughout JavaScript.

Variables

A variable is a container whose contents can change. Declaring one with let:

let name = "Craig";
name; // "Craig"

name = "John";
name; // "John"

A constant, declared with const, can be assigned a value once and never reassigned:

const maxQuantity = 5;
maxQuantity = 3; // TypeError: Assignment to constant variable.
KeywordReassignable?Typical use
letYesValues expected to change over time
constNoValues meant to remain fixed once set (placeholders for constants)

Operators

The + operator performs arithmetic on numbers, but on strings it performs concatenation — sticking two strings together:

"Craig" + "Shoemaker"
// "CraigShoemaker"

"Craig " + "Shoemaker"
// "Craig Shoemaker"

A single equals sign (=) is the assignment operator, not a test for equality — it is what you use when declaring or updating a variable (let x = 1;). Trying to write 1 + 1 = 2 produces an error, because the interpreter attempts (and fails) to assign a value to the expression 1 + 1.

To test equality, JavaScript uses == or (preferably) ===, which returns a Boolean value (true/false):

1 + 1 == 2   // true
1 + 2 == 2   // false
OperatorMeaning
+Addition (numbers) or concatenation (strings)
=Assignment
==Loose equality (performs type coercion)
===Strict equality (recommended default)

Control Flow: Blocks and Scope

Control flow describes how a program executes — and sometimes skips — lines of code, giving the illusion of “decision making.” The basic building block of control flow is a block, delimited by curly braces { }.

let name = "Craig";
console.log(name); // Craig

Declaring the same variable name twice with let in the same scope produces an error (Identifier 'name' has already been declared). However, let gives variables block scope — wrapping a second declaration in its own { } block isolates it from the outer scope:

let name = "Craig";

{
  let name = "Tyler"; // valid: separate block scope
}

console.log(name); // Craig — the outer variable is unaffected

Control Flow: if/else Conditionals

An if statement (conditional) lets a program make decisions based on whether an expression evaluates to true or false:

let name = "Craig";

if (name === "Craig") {
  console.log("Hi Craig!");
} else if (name === "Tyler") {
  console.log("Tyler, is that you?");
} else {
  console.log("Wait, where's Craig?");
}

Prefer the strict equality operator (===) inside conditionals over ==, since == performs type coercion with sometimes surprising results. if/else if/else chains let you test multiple conditions in sequence, falling through to a final default else branch when none of the earlier conditions match.

flowchart TD
    Start([Evaluate name]) --> Q1{name === "Craig"?}
    Q1 -- true --> A["console.log('Hi Craig!')"]
    Q1 -- false --> Q2{name === "Tyler"?}
    Q2 -- true --> B["console.log('Tyler, is that you?')"]
    Q2 -- false --> C["console.log(\"Wait, where's Craig?\")"]

Function Declarations

Repeating the same block of logic in multiple places creates a maintenance problem: any bug fix or change has to be made in every copy. Wrapping reusable logic in a function solves this:

function greet() {
  let name = "Craig";
  if (name === "Craig") {
    console.log("Hi Craig!");
  } else {
    console.log("Wait, where's Craig?");
  }
}

greet(); // Hi Craig!
greet(); // Hi Craig! (can be called repeatedly)

Declaring a function only defines it — nothing runs until the function is called using its name followed by parentheses (greet();). Convention indents nested blocks of code (the function body, and any blocks nested further inside it, such as if blocks) for readability.

Function Arguments

A function becomes flexible once you can pass data into it via arguments, instead of relying on hard-coded values inside the function body:

function greet(name) {
  if (name === "Craig") {
    console.log("Hi Craig!");
  } else if (name === "Tyler") {
    console.log("Tyler, is that you?");
  } else {
    console.log("Wait, where's Craig?");
  }
}

greet("Craig"); // Hi Craig!
greet("Tyler"); // Tyler, is that you?
greet("Jacob"); // Wait, where's Craig?

Callers can also pass in variables rather than hard-coded literals:

let name = "Craig";
greet(name); // Hi Craig!

Function Return Values

Data can also come out of a function using the return keyword. Whatever follows return becomes the function’s result when it is called:

function greet(name) {
  let result = "";

  if (name === "Craig") {
    result = "Hi Craig!";
  } else if (name === "Tyler") {
    result = "Tyler, is that you?";
  } else {
    result = "Wait, where's Craig?";
  }

  return result;
}

let message = greet("Craig");
console.log(message); // Hi Craig!

Declaring the result variable at the top of the function with a sensible default (here, an empty string), then returning it as the final statement, is a common and readable pattern. Returning a value (rather than only calling console.log inside the function) means the same greet logic can be reused anywhere — printed to the console, displayed on the page, or passed to other functions — a principle known as separation of concerns: the function is only concerned with producing a greeting, not with what happens to that greeting afterward.

Functions with Multiple Arguments

Functions can accept zero, one, or many arguments:

function add(number1, number2) {
  return number1 + number2;
}

let sum = add(2, 2);
console.log(sum); // 4

Object Literals

An object literal groups related data (and, optionally, behavior/functions) together into a single value, rather than tracking several loose variables:

let firstName = "Craig";
let lastName = "Shoemaker";
let twitter = "@craigshoemaker";

// grouped as an object literal:
let person = {
  firstName: "Craig",
  lastName: "Shoemaker",
  twitter: "@craigshoemaker",
  greet: function () {
    console.log("My name is " + this.firstName);
  }
};

console.log(person);          // logs the whole object
console.log(person.firstName); // "Craig" — dot notation accesses a property
person.greet();                 // "My name is Craig"

Inside an object’s own function, the this keyword refers to the object itself, giving access to its other properties (such as this.firstName above). Object properties are separated by commas, not semicolons, since they are part of one larger declaration rather than separate statements.

Arrays

An array represents an ordered list of values, declared with square brackets:

let numbers = [100, true, "JavaScript"];

console.log(numbers);      // [100, true, "JavaScript"]
console.log(numbers[0]);   // 100 — arrays are zero-indexed
console.log(numbers.length); // 3

numbers.push(42);          // adds an item, returns the new length

Arrays can mix data types (numbers, Booleans, strings, and more) freely within the same list. Each item’s position is called its index, and arrays are zero-indexed — the first item is at index 0.

Array capabilityExample
Access by indexnumbers[0]
Lengthnumbers.length
Add an itemnumbers.push(value)
Iteratefor, while, or forEach (see below)

while Loops

A loop repeats a block of code — it lets a program iterate. A while loop repeats its body while its test expression remains true:

let count = 0;
const max = 10;

while (count < max) {
  console.log(count);
  count = count + 1;
}
// logs 0 through 9

Because count starts at 0 and the loop stops once count is no longer less than max, the loop body runs exactly 10 times, printing 0 through 9 (not 1 through 10) — the same zero-indexed reasoning seen with arrays. Forgetting to increment the counter would create an infinite loop.

for Loops

A for loop packs the initializer, the test condition, and the increment step into a single, more compact line:

for (let i = 0; i < 10; i++) {
  console.log(i);
}
// logs 0 through 9 — identical result to the while loop above

i++ is shorthand for i = i + 1. The conventional counter name i stands for index. A very common pattern combines a for loop with an array’s length to visit every item:

let numbers = [1, 2, 3];

for (let i = 0; i < numbers.length; i++) {
  console.log(numbers[i]);
}
// logs 1, 2, 3

The forEach Loop

Arrays provide a built-in forEach method that iterates over every item without manually managing an index variable:

let days = ["Monday", "Tuesday", "Wednesday"];

days.forEach((day, index) => {
  console.log(`day: ${day}, index: ${index}`);
});
// day: Monday, index: 0
// day: Tuesday, index: 1
// day: Wednesday, index: 2

forEach passes the current item (day) and its position (index) into an anonymous function — a function with no name, defined inline as the argument to forEach. The example above also introduces template strings: text wrapped in back ticks (`) that can embed variables directly using ${expression} syntax, instead of concatenating with +.

Loop typeBest suited for
whileRepeating while a condition holds, when the number of iterations is not known up front
forRepeating a known/counted number of times; classic indexed iteration
forEachIterating every item of an array with clean, minimal syntax

Debugging JavaScript in the Browser

The browser’s debugger lets you pause code execution at a specific line and inspect what is happening at that moment — one of the most valuable tools available in the developer tools.

Given a small script with a bug (referencing an undeclared variable firstName instead of first):

let first = "Craig";
let last = "Shoemaker";
console.log(firstName + " " + last); // bug: should be "first"

Steps to debug in the browser:

  1. Open DevTools and switch to the Sources tab.
  2. Click a line number to set a breakpoint — a point where execution pauses.
  3. Reload the page; execution stops at the breakpoint, and you can hover over variables to inspect their current values.
  4. Use the stepping controls to advance execution.
Debugger controlEffect
Resume script executionContinues running until the next breakpoint (or the end)
Step over next function callExecutes the current line and moves to the next, without entering called functions
Step into function callEnters the function being called on the current line
Step out of function callFinishes the current function and returns to its caller

Once the mistaken variable name is spotted (firstName should be first), the fix can be made in the editor while still paused, and re-running confirms the corrected value.

Selecting Elements

To manipulate a page from JavaScript, you first need to select the element(s) you want to work with. Given two card elements on a page:

<section id="craig-shoemaker" class="card">
  <h2>Craig Shoemaker</h2>
  <ul>
    <li><a href="https://twitter.com/craigshoemaker">Twitter</a></li>
  </ul>
</section>
<section id="john-papa" class="card">
  <h2>John Papa</h2>
</section>
document.getElementById("craig-shoemaker"); // matches by unique id
document.getElementById("john-papa");

document.querySelector(".card");     // returns the FIRST matching element
document.querySelectorAll(".card");  // returns ALL matching elements as a NodeList

document.querySelector('a[href="https://twitter.com/craigshoemaker"]');

querySelector/querySelectorAll accept the same kind of selector syntax used in CSS rules. When nesting quotes (a selector string containing an attribute value that is itself quoted), use single quotes on the outside and double quotes inside, or vice versa — just be consistent.

Selection methodReturns
document.getElementById(id)A single element matching the given id
document.querySelector(selector)The first element matching a CSS-style selector
document.querySelectorAll(selector)A NodeList of every matching element

Working with Element Attributes

Once an element is selected, you can read, set, or remove its attributes:

const links = document.querySelectorAll(".card a");

links.forEach(link => {
  link.setAttribute("target", "_blank"); // opens each link in a new tab
});

setAttribute either creates a new attribute or updates an existing one. The complementary functions:

$0.getAttribute("target"); // reads a value ("$0" refers to the currently-selected DevTools element)
$0.getAttribute("href");
$0.removeAttribute("target"); // removes an attribute entirely

The classList API

The class attribute is special-cased in the DOM: because class is a reserved word in JavaScript, you cannot do element.class = .... Instead, the classList API manages an element’s classes:

const card = document.querySelector(".card");

card.classList.add("dark");     // adds a class ("card dark")
card.classList; // ["card", "dark"]

card.classList.remove("dark");  // removes it again ("card")

Adding a class such as dark that has a corresponding CSS rule (e.g. .dark { background-color: #222; color: #fff; }) instantly re-themes the element without touching the underlying HTML markup.

The Script Tag

The <script> element is where you execute JavaScript on a page. Its location in the document matters: because HTML and CSS are responsible for the structure and appearance of a page, and browsers process a page from top to bottom, <script> elements are conventionally placed near the very bottom of <body>, just before the closing </body> tag — this way, the page’s structure and styling are not held up waiting on a script.

<body>
  <!-- page content -->
  <script src="index.js" type="text/javascript"></script>
</body>

Moving JavaScript into its own external file (as with CSS) has two main benefits:

  1. Code is easier to write and maintain in a dedicated file, and multiple pages can share the same script.
  2. The browser can cache the file, so pages load faster after the first request.

Unlike some self-closing tags (such as <meta>), the <script> element always requires an explicit closing tag, even when it has no inline content and only references an external file via src.

The Window Object

The window object is the top-level context for scripts running in a browser tab; it hosts the document object (the DOM for that tab). Opening the same page in a second tab creates an entirely separate, independent window — even though both tabs show the same site.

window exposes many browser-level functions and properties, including window.location, which provides information about the current URL and its individual segments. In this course, window is primarily used to gain access to browser built-ins (as in the next module’s window.addEventListener('DOMContentLoaded', ...)).

Module 6: Building Bethany’s Pie Shop

This module puts everything from the previous modules into practice: HTML, CSS, and JavaScript are combined to build Bethany’s Pie Shop end to end. Key features of the finished page: a liquid layout that expands and contracts proportionally as the browser is resized, a mobile layout where the logo disappears and the hero image is cropped differently, and a working order-total calculator.

Project Setup and HTML Boilerplate

Starting from a blank index.html, typing the Emmet abbreviation html:5 and pressing Tab scaffolds a base HTML5 document:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    
</body>
</html>

Breaking down the boilerplate:

Line/ElementPurpose
<!DOCTYPE html>Declares the document as HTML5
<html lang="en">The root element; lang="en" tells search engines and tools the page’s primary language
<head>Holds metadata: title, character set, viewport, and other non-visible information
<meta charset="UTF-8">Character encoding, supporting a very wide range of characters and languages
<meta name="viewport" ...>Controls initial scaling/rendering width on mobile devices
<body>The visible content of the page

For Bethany’s Pie Shop, the <title> is updated and a favicon link is added:

<title>Bethany's Pie Shop</title>
<link rel="icon" type="image/png" href="favicon.png">

Building the Header and Navigation

The page header uses Emmet’s header>nav>ul>li*3>a shorthand to scaffold a <header> containing a <nav>, an unordered list, and three list items each with a link. The first list item is then swapped out for Bethany’s logo image instead of a text link:

<header>
    <nav>
        <ul>
            <li>
                <img src="images/logo.png"
                    width="150"
                    height="47"
                    alt="Bethany's Pie Shop company logo">
            </li>
            <li><a href="pies.html">Pies</a></li>
            <li><a href="about.html">About</a></li>
        </ul>
    </nav>
</header>

The pies.html and about.html destination pages are referenced but not built as part of this course — they simply illustrate how to link between pages within a site.

The site footer uses an Emmet expression combining footer>address>(h2+p+p) — the + sign (rather than >) places sibling elements next to each other instead of nesting them:

<footer>
    <address>
        <h2>Bethany's Pie Shop</h2>
        <p>Bakery Street 555</p>
        <p>Brussels, Belgium</p>
    </address>
    <p>All rights reserved &copy; 2023 Bethany's Pie Shop</p>
    <p>Contact us via <a href="mailto:info@bethanyspieshop.com">email</a></p>
</footer>

The Aside and Logo Column

Looking at the finished layout, the page is split into two columns: a left column with Bethany’s icon logo, and a right column with the main content. On mobile, the left column disappears entirely. The structure to support this two-column behavior uses the semantic <main>, <article>, and <aside> elements:

  • <main> / <article> signal to search engines that this is the most important content on the page.
  • <aside> signals that its content — here, the decorative logo — is not as important to the page’s core message.
<aside>
    <div class="logo-container">
        <img src="images/bethanylogo.png"
            alt="Bethany's Pie Shop Logo"
            width="120" height="140">
    </div>
</aside>

The Responsive Picture Element

The hero image crops differently depending on device size — not simply hidden/shown or resized, but an entirely different image file is chosen by the browser for different screen widths. This is done using the <picture> element together with a media-query-driven <source>:

<picture>
    <source media="(min-width: 1000px)" 
      srcset="images/bethany-700.jpg" />
    <img src="images/bethanysmall.jpg" alt="Bethany of Bethany's Pie Shop" />
</picture>

The <picture> element is a container that can hold several candidate image sources, each constrained by attributes like resolution, format, or a media query. The browser evaluates each <source>’s media attribute in order and renders the first one that matches the screen; if none match, it falls back to the plain <img> child. Here, if the viewport is 1000px or wider, bethany-700.jpg is used; otherwise the browser falls back to the smaller bethanysmall.jpg.

Title Text and the Order Calculator Table

The article’s heading and body copy are added, followed by the Order Calculator table. A table is built from rows (<tr>) and cells — header cells (<th>) and data cells (<td>):

<article>
    <h1>Welcome to Bethany's Pie Shop</h1>

    <picture><!-- as above --></picture>

    <h2>Our history</h2>
    <p>For many years, Bethany has been baking the most delicious pies at her home.</p>

    <div id="calculator">
        <h2>Order Calculator</h2>
        <table>
            <thead>
                <tr>
                    <th>Type</th>
                    <th>Price</th>
                    <th>Quantity</th>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>Apple</td>
                    <td>$12.00</td>
                    <td><input type="number" id="apple" min="0"></td>
                </tr>
                <tr>
                    <td>Cherry</td>
                    <td>$15.00</td>
                    <td><input type="number" id="cherry" min="0"></td>
                </tr>
                <tr>
                    <td>Strawberry</td>
                    <td>$18.00</td>
                    <td><input type="number" id="strawberry" min="0"></td>
                </tr>
                <tr>
                    <td></td>
                    <td>Total:</td>
                    <td id="total-container">$0.00</td>
                </tr>
            </tbody>
        </table>
    </div>
</article>

Each pie’s <input type="number" min="0"> restricts entry to numeric, non-negative values. The blank first cell in the totals row exists purely to keep the table’s three-column structure consistent even though it has no content. The id values (apple, cherry, strawberry, total-container) are deliberately chosen so JavaScript can locate and update them later.

Linking the CSS File

Rather than styling inline, all CSS rules are placed in an external styles.css file, linked from the <head>:

<head>
  <link rel="stylesheet" href="styles.css">
</head>

Placing rules in a separate .css file allows the file to be cached by the browser (making the site faster) and keeps rules easier to write, read, and maintain than styles embedded directly in the markup.

CSS Custom Properties (Variables)

Just as JavaScript has variables, CSS supports custom properties (“CSS variables”) — reusable named values declared once and referenced throughout a stylesheet. They are declared under a :root selector so they are available everywhere:

:root {
    --color-brand-primary: #a1603b;
    --color-brand-contrast: #fff;
    --color-medium-gray: #898989;
    --color-light-gray: #ccc;
    --color-background: #E5E5E5;
    --font-body: 'Segoe UI', Arial, Helvetica, sans-serif;
}

A custom property name is arbitrary but must be prefixed with --. It is referenced elsewhere using the var() function:

header {
    background-color: var(--color-brand-primary);
}

Visual Studio Code shows a small color preview next to recognized hex values, which is a handy visual aid while tuning a palette like this one.

Resetting Browser Defaults

Different browsers ship with slightly different default styles (padding on <body>, margin on <html>, and so on). To get a consistent baseline across browsers, the base elements are reset to 0 margin/padding and given the shared body font and background color:

body, html {
    margin: 0;
    padding: 0;
    font-family: var(--font-body);
    background-color: var(--color-background);
}

This removes any unwanted default spacing around the edges of the page and establishes the font and background used throughout the rest of the site.

Styling the Navigation

The <nav> list is reset (removing default list spacing) and then styled so each link behaves as an inline navigation item rather than a stacked list:

nav ul {
    margin: 0;
    padding: 0;
}

nav > ul > li {
    display: inline-block;
    list-style: none;
    margin: 0 1.5em 0 1.5em;
    vertical-align: middle;
}

nav > ul > li:first-child {
    margin: .5em;
    padding: 0;
}

nav ul li a,
nav ul li a:active,
nav ul li a:hover,
nav ul li a:visited {
    color: var(--color-brand-contrast);
    text-decoration: none;
    text-transform: uppercase;
    font-weight: 600;
    font-size: 1.25em;
}

Key details:

  • The > child combinator restricts the rule to list items that are direct children of the navigation’s unordered list.
  • display: inline-block lets list items sit side by side while still allowing width, height, margin, and padding to be controlled — a hybrid of inline (which ignores box dimensions) and block (which forces its own line).
  • list-style: none removes the default bullet markers, appropriate for a navigation menu.
  • The margin shorthand 0 1.5em 0 1.5em follows the CSS order top, right, bottom, left.
  • Stacking the link states (a, a:active, a:hover, a:visited) into one rule normalizes link appearance so navigation links do not change color when clicked or visited — unlike typical body-text links.
  • 1.25em is a relative unit: 1em equals the parent element’s font size, so 1.25em renders at 125% of that inherited size.

Styling the Heading

A small amount of color and spacing rounds out the header area and prepares the <article> for the layout rules that follow:

h1 {
    color: var(--color-brand-primary);
}

article {
    padding: 2em;
}

Styling the Order Calculator

The calculator’s container gets a subtle shadow, rounded corners, a constrained width, and background color, all driven by the custom properties defined earlier:

#calculator {
    box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.2);
    border: solid 1px var(--color-medium-gray);
    border-radius: 1em;
    padding: 1.5em;
    width: 90%;
    max-width: 325px;
    background-color: var(--color-brand-contrast);
    margin-top: 2em;
}

#calculator h2 {
    margin-top: 0;
}

#calculator div {
    margin: .5em 0 .5em 0;
}

#calculator input {
    padding: 5px;
    border-radius: 3px;
    border: solid 1px var(--color-medium-gray);
    width: 150px;
}
box-shadow valueMeaning
2px (1st)Horizontal offset of the shadow
2px (2nd)Vertical offset of the shadow
8pxBlur radius — how far the shadow spreads/softens
rgba(0, 0, 0, 0.2)Shadow color: black at 20% opacity (rgba = red, green, blue, alpha/transparency)

width: 90% lets the calculator scale with its container on small screens, while max-width: 325px prevents it from growing too large on wide screens.

footer {
    margin: 3em 0;
    padding: 1em;
    background-color: var(--color-light-gray);
}

footer address {
    font-style: normal;
}

footer address h2,
footer address p {
    margin: 0;
}

footer address h2 {
    font-size: 1.25em;
}

Browsers italicize the <address> element by default; font-style: normal overrides that. Removing the default margins from the heading and paragraphs inside the footer’s address block makes those lines stack tightly rather than with large gaps between them, and the shop name is bumped to 1.25em for a bit more visual emphasis.

Building the Responsive Layout with Flexbox

The mobile-first baseline hides the logo container entirely, since the logo is unnecessary overhead on a small screen:

.logo-container {
    display: none;
}

A media query then activates the desktop, two-column layout once the viewport reaches 960px:

@media (min-width:960px)  {
    main {
        display: flex;
        flex-direction: row-reverse;
    }

    article, aside {
        display: block;
        flex-basis: 0;
        flex-shrink: 1;
        padding: 0.75rem;
    }

    article {
        flex: 4;
        padding-right: 6em;
    }

    aside {
        flex: 1;
    }

    footer {
        padding-left: 20%;
    }

    .logo-container {
        display: block;
        text-align: center;
        padding-top: 2em;
    }

    .logo-container img {
        max-width: 120px;
    }
}

Explaining the key choices:

  • flex-direction: row-reverse — in the HTML, <article> (main content) comes before <aside> (logo) so search engines encounter the important content first. Visually, however, the design calls for the logo on the left. row-reverse flips the visual order without changing the semantic/source order.
  • flex-basis: 0 on both columns means neither starts with a predefined size — both grow purely according to the flex ratio given to each.
  • flex-shrink: 1 allows both columns to shrink proportionally if the container is too narrow to fit their full preferred size.
  • flex: 4 on <article> and flex: 1 on <aside> divide the row into 5 total units — the article claims 4 of them, the aside claims 1 — producing the wide-content / narrow-logo column split seen in the final design.
  • rem (“root em”) is used for some padding values so that if the root font size ever changes globally, this padding scales proportionally with it.
  • Inside the media query, .logo-container switches from display: none to display: block (revealing it only on desktop), is centered with text-align: center, and its image is capped at max-width: 120px so it never grows too large as the screen widens.
flowchart LR
    subgraph Desktop["Desktop layout (>= 960px), main { flex-direction: row-reverse }"]
        direction LR
        Aside["aside (logo)<br/>flex: 1"] --- Article["article (content)<br/>flex: 4"]
    end
flowchart TD
    M["main"] -->|"< 960px"| Stacked["Single column: logo hidden,<br/>article content only"]
    M -->|">= 960px"| TwoCol["Two-column Flexbox row:<br/>logo (1 unit) + content (4 units)"]

With these rules in place, resizing the browser window shows the layout staying proportional (a “liquid” layout), while the mobile emulation view shows the simpler, single-column, logo-free presentation.

Linking the Script File

The JavaScript file is referenced with a <script> tag placed just before the closing </body> tag, ensuring the HTML content is fully loaded into the browser before the script runs:

    <script src="script.js"></script>
</body>
</html>

Keeping JavaScript in its own external file (as with CSS) enables browser caching, makes debugging easier, and lets the editor provide better tooling for a file dedicated to one type of code.

Modeling the Inventory Object

Bethany’s inventory of pies is modeled as a single JavaScript object, with a nested object per pie type tracking its price and the customer’s requested quantity:

const inventory = {
    apple: { price: 12, qty: 0 },
    cherry: { price: 15, qty: 0 },
    strawberry: { price: 18, qty: 0 }
};

Individual values can be read either with dot notation (inventory.apple.price) or bracket notation (inventory["apple"].price) — the bracket form becomes essential once the property name itself is stored in a variable, as it is in the calculation logic that follows.

The Sum Function

The sum function calculates the running order total across every pie in the inventory:

function sum() {
    let total = 0;
    const keys = Object.keys(inventory);
    keys.forEach(key => {
        total += inventory[key].price * inventory[key].qty;
    });
    return total;
}

Rather than hard-coding inventory.apple, inventory.cherry, and inventory.strawberry explicitly (which would break the moment a new pie type, such as pumpkin, is added), the function asks the inventory object for its own property names via Object.keys(inventory). It then uses forEach with an arrow function (key => { ... }, a more concise alternative to a full function (key) { ... } expression) to loop over every key, using bracket notation (inventory[key]) to look up each pie’s price and quantity dynamically.

The total += ... shorthand is equivalent to total = total + ... — it accumulates a running total across each iteration of the loop, rather than overwriting total with only the last pie’s contribution.

sequenceDiagram
    participant caller as Calling code
    participant sum as sum()
    participant inv as inventory object
    caller->>sum: sum()
    sum->>inv: Object.keys(inventory)
    inv-->>sum: ["apple", "cherry", "strawberry"]
    loop for each key
        sum->>inv: inventory[key].price * inventory[key].qty
        inv-->>sum: partial amount
        sum->>sum: total += partial amount
    end
    sum-->>caller: return total

The Calculate Function

The calculate function is called whenever a customer changes the quantity in one of the input boxes:

function calculate(box) {
    let qty = 0;

    if(box.value.length > 0) {
        qty = parseInt(box.value);
    }

    inventory[box.id].qty = qty;

    const total = sum();
    return `$${total}.00`;
}

Key details:

  • box is the specific <input> element that triggered the change.
  • Even though the <input> is declared type="number", its .value is always read as a string; box.value.length > 0 checks whether the user has typed anything at all.
  • parseInt(box.value) converts that string into an actual number so it can be used in arithmetic.
  • Because each input’s id (apple, cherry, strawberry) exactly matches a key in the inventory object, inventory[box.id].qty = qty; updates the correct pie’s quantity dynamically, without needing an if/else chain to figure out which pie changed.
  • The final total is formatted as a dollar amount using a template string: `$${total}.00`.

Listening for DOMContentLoaded

Rather than running immediately (before the page’s HTML elements exist), the remaining setup code waits for the DOMContentLoaded event — fired once the HTML has been parsed and is available to JavaScript:

window.addEventListener('DOMContentLoaded', () => {

    const totalContainer = document.getElementById('total-container');
    const inputBoxes = document.querySelectorAll('#calculator input');

    // event wiring goes here, see next section

});

Wrapping the element lookups and event wiring inside this listener ensures they only run once the page has finished loading — the inventory, sum, and calculate functions/objects, by contrast, can be declared at the top level since they do not depend on the DOM being ready.

Wiring Up Box Event Listeners

querySelectorAll returns a NodeList (not a true array, but one that does support forEach) containing every quantity input box inside the calculator. Each box is given listeners for both the change and keyup events:

window.addEventListener('DOMContentLoaded', () => {

    const totalContainer = document.getElementById('total-container');
    const inputBoxes = document.querySelectorAll('#calculator input');

    inputBoxes.forEach(box => {

        box.addEventListener('change', () => {
            totalContainer.textContent = calculate(box);
        });

        box.addEventListener('keyup', () => {
            totalContainer.textContent = calculate(box);
        });

    });

});

Both event types matter for a good user experience:

EventFires whenWhy it is needed here
changeThe input loses focus after its value changedCatches cases like using arrow keys or losing focus without a keystroke
keyupAny key is released while typing in the boxUpdates the total live, as the customer types, instead of waiting until they click or tab away
sequenceDiagram
    participant User
    participant Box as input#apple
    participant Listener as keyup/change listener
    participant Calc as calculate(box)
    participant Sum as sum()
    participant Total as #total-container

    User->>Box: types "2"
    Box-->>Listener: keyup event fires
    Listener->>Calc: calculate(box)
    Calc->>Calc: parseInt(box.value) -> 2
    Calc->>Calc: inventory[box.id].qty = 2
    Calc->>Sum: sum()
    Sum-->>Calc: total (number)
    Calc-->>Listener: "$total.00"
    Listener->>Total: textContent = "$total.00"

Debugging the Calculator Step by Step

Stepping through the finished calculator with the browser’s debugger illustrates exactly how the pieces fit together:

  1. Set a breakpoint on the first line inside the DOMContentLoaded handler, inside the forEach loop wiring up event listeners, and inside the keyup handler.
  2. Reload the page — execution pauses at DOMContentLoaded. Stepping over confirms totalContainer now references the element with id="total-container".
  3. querySelectorAll('#calculator input') returns all three quantity boxes; stepping into the forEach call shows the change and keyup listeners being attached, once per box (three times total).
  4. Typing a value (for example, 1) into the Apple box fires keyup; stepping into calculate(box) shows box.value.length evaluating to 1 (greater than 0), so parseInt(box.value) converts the string "1" into the number 1.
  5. inventory[box.id].qty = qty; updates inventory.apple.qty from 0 to 1, confirmed by inspecting the inventory object.
  6. Stepping into sum() shows total starting at 0, then accumulating 12 for the apple pie (price 12 * qty 1), 0 for cherry (qty still 0), and 0 for strawberry — the running total settles at 12.
  7. Back in calculate, the template string formats the result as "$12.00", which is written into #total-container’s textContent, and the calculator visibly displays $12.00.

This walkthrough demonstrates the complete flow: an HTML input event → a JavaScript event listener → the calculate function → the sum function reading the inventory object → a formatted string written back into the DOM.

Summary

This course walked through the three foundational technologies of the web — HTML, CSS, and JavaScript — building up from a single <h1> element to a complete, responsive, interactive website: Bethany’s Pie Shop.

Key principles to carry forward:

  • HTML provides structure and meaning. Elements are made of tags and attributes; using the right semantic element (e.g. <h1> vs. a plain <p>, <main> vs. <aside>) communicates significance to both browsers and search engines, in addition to giving the browser a structure to render — represented internally as the DOM, a tree of objects.
  • CSS provides style, layout, and responsiveness. Selectors (tag, class, ID, attribute, combinators) target elements; rules cascade based on source order and specificity; some properties inherit and some do not; the box model (content, padding, border, margin) governs every element’s rendered size; Flexbox and media queries together produce layouts that adapt fluidly from mobile to desktop.
  • JavaScript provides interactivity. Variables (let/const), operators, control flow (if/else), functions (with arguments and return values), object literals, arrays, and loops (while, for, forEach) are the building blocks; the DOM can be queried (getElementById, querySelector(All)), read and modified (attributes, classList), and wired up to user interaction through events (DOMContentLoaded, change, keyup).
  • The browser’s developer tools are indispensable at every stage — inspecting the DOM and Styles panel, live-editing CSS, and stepping through JavaScript with breakpoints all shorten the feedback loop dramatically compared to guessing, saving, and reloading.
  • Validate and check compatibility as you go — the W3C validator confirms your HTML follows the specification, and caniuse.com confirms a CSS feature is safe to rely on across target browsers.

Quick-Reference: Core Concepts by Technology

TechnologyCore building blocksKey tools
HTMLElements, tags, attributes, semantic structure, the DOMvalidator.w3.org, browser Elements panel
CSSSelectors, specificity, cascade, inheritance, box model, Flexbox, media queriescaniuse.com, browser Styles panel
JavaScriptVariables, operators, control flow, functions, objects, arrays, loops, DOM selection, eventsBrowser Console, Sources panel/debugger

Completion Checklist

  • Comfortable writing valid HTML5 boilerplate and common elements (headings, paragraphs, links, images, tables, semantic sectioning elements).
  • Understand the difference between an element’s tag and its attributes, and the special roles of id and class.
  • Able to explain the DOM as the browser’s in-memory tree representation of a page.
  • Can write CSS selectors of every major type (tag, class, ID, attribute, list, child combinator) and predict which rule wins under the cascade and specificity rules.
  • Understand the box model (content, padding, border, margin) and the effect of box-sizing: border-box.
  • Can build a simple responsive, multi-column layout using Flexbox and media queries, mobile-first.
  • Comfortable with core JavaScript: variables, operators, conditionals, functions (arguments and return values), object literals, arrays, and all three loop styles.
  • Can select DOM elements and read/update attributes and classes from JavaScript.
  • Can wire up event listeners (DOMContentLoaded, change, keyup) to make a page interactive.
  • Comfortable using browser DevTools to inspect elements, live-edit styles, and step through JavaScript with breakpoints.

Search Terms

html · css · fundamentals · web · frontend · development · selectors · function · element · elements · object · responsive · styling · browser · calculator · javascript · style · anatomy · arguments · attributes · bethany · box · control · debugging

Interested in this course?

Contact us to book it or get a custom training plan for your team.