Loading
All articles
Programming 14 min read

Frontend Web Development: A Visual, Hands-On Guide

Learn frontend web development visually: build a real app one layer at a time with HTML, CSS and JavaScript, fix a classic bug, see what the DOM is, what breaks without each layer, how to fit any screen, why accessibility matters, and exercises.

nbkhope
On this page

In The Parts of Web Development you saw that a web app has a side the user sees and a side they don't. This guide is about the first one, the frontend, which is made of three elements: HTML, CSS and JavaScript.

The best way to understand them is to watch each one arrive. You'll build a small tip calculator one layer at a time, and take a screenshot at every stage. You'll then fix a classic bug, see what JavaScript really changes, remove each layer to find out what breaks, fit the page to a phone, and test it for people who don't use a mouse. Exercises wrap it up.

All results below come from real runs in Chrome on Windows. You only need a text editor and a browser.

Three layers, three jobs

Each element has one job, and they stack on top of each other:

The three layers of the frontend. HTML is the structure, the skeleton of the page, and it says what each thing is. CSS is the style on top of it, which sets colors, fonts, spacing and position. JavaScript is the interaction on top of that, which responds to the user and changes the page. HTML and CSS alone give a static website, and JavaScript turns it into an application

Layer 1: HTML, the structure

HTML is a markup language. It defines the fundamental structure of a page, and you write it with tags. Create a new folder, and save this as index.html:

1<!doctype html>
2<html lang="en">
3  <head>
4    <meta charset="utf-8">
5    <meta name="viewport" content="width=device-width, initial-scale=1">
6    <title>Tip Calculator</title>
7  </head>
8  <body>
9    <main>
10      <h1>Tip Calculator</h1>
11      <form id="form">
12        <label for="bill">Bill</label>
13        <input id="bill" type="number" min="0" step="0.01" value="100">
14        <label for="percent">Tip (%)</label>
15        <input id="percent" type="number" min="0" value="15">
16        <button>Calculate</button>
17      </form>
18      <p id="result">Enter the bill and press Calculate.</p>
19    </main>
20  </body>
21</html>

Each tag can carry extra information in attributes:

The parts of an HTML element. In label for="bill", Bill, closing label, there's an opening tag, an attribute with a name and a value, the content, and a closing tag. In input id="bill" type="number", there are several attributes and no closing tag, because an input has no content

Double-click the file to open it in your browser. It's plain, and it's all there. The heading is a heading, and the fields are real fields you can type in:

The tip calculator with only HTML. A large black heading, then the Bill and Tip fields and a Calculate button in one line, in the browser's default font on a white page

If you want to go deeper, there's a whole guide on what HTML is.

Layer 2: CSS, the style

While HTML defines the basic structure, CSS defines the style: the colors, the fonts, the spacing and the position of each element. Save this as style.css:

1body {
2  margin: 0;
3  padding: 24px;
4  background: #e3eefb;
5  font-family: system-ui, sans-serif;
6}
7
8main {
9  max-width: 420px;
10  margin: 0 auto;
11  padding: 24px;
12  border-radius: 12px;
13  background: white;
14}
15
16h1 {
17  margin-top: 0;
18  color: #1a4f91;
19}
20
21form {
22  display: grid;
23  grid-template-columns: 1fr 1fr;
24  gap: 12px;
25  align-items: center;
26}
27
28input {
29  padding: 8px;
30  font-size: 16px;
31}
32
33button {
34  grid-column: 1 / -1;
35  padding: 12px;
36  border: none;
37  border-radius: 8px;
38  background: #2a78d6;
39  color: white;
40  font-size: 16px;
41}
42
43#result {
44  font-size: 20px;
45  font-weight: bold;
46}
47
48@media (max-width: 400px) {
49  form {
50    grid-template-columns: 1fr;
51  }
52}

Each rule has a selector, which picks the elements, and declarations, which say how they look. Connect the file by adding this line inside <head>:

<link rel="stylesheet" href="style.css">

Refresh the page:

The tip calculator with CSS. A white rounded card on a light blue page, with a blue heading, the fields aligned in two columns and a wide blue Calculate button. The message below still says to enter the bill

It looks like a finished product now. Press Calculate, though. The page reloads, the address gets a ? at the end, and the message stays the same. HTML and CSS together give you a static website: it can show things, and it can't respond. To learn more, see what CSS is.

Layer 3: JavaScript, the interaction

A website only becomes an application once it interacts with the user, and that comes from scripts written in JavaScript. Save this as script.js:

1const form = document.querySelector("#form");
2const billInput = document.querySelector("#bill");
3const percentInput = document.querySelector("#percent");
4const result = document.querySelector("#result");
5
6form.addEventListener("submit", (event) => {
7  event.preventDefault();
8  const bill = billInput.value;
9  const tip = bill * percentInput.value / 100;
10  const total = bill + tip;
11  result.textContent = `Tip: ${tip}. Total: ${total}.`;
12});

Connect it by adding this line just before </body>:

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

The script finds the elements, then waits for an event: the form being submitted. preventDefault cancels the reload you saw before. Refresh and press Calculate:

Tip: 15. Total: 10015.

The page responds, and the answer is wrong. A 100 bill plus a 15 tip isn't 10015.

The bug in value

The value of an input is always text, even with type="number". So bill is the string "100". The * operator converts it to a number, which is why the tip is right. The + operator joins strings, so "100" + 15 gives "10015". It's the same bug that opens the TypeScript series.

The fix is to convert the text to numbers yourself. Change the script to this:

1const form = document.querySelector("#form");
2const billInput = document.querySelector("#bill");
3const percentInput = document.querySelector("#percent");
4const result = document.querySelector("#result");
5
6form.addEventListener("submit", (event) => {
7  event.preventDefault();
8  const bill = Number(billInput.value);
9  const tip = bill * Number(percentInput.value) / 100;
10  const total = bill + tip;
11  result.textContent = `Tip: ${tip.toFixed(2)}. Total: ${total.toFixed(2)}.`;
12});
Tip: 15.00. Total: 115.00.
The finished tip calculator. The same card as before, and below the button the message now says Tip: 15.00. Total: 115.00

That's the pattern behind almost every interaction on the web:

What happens when you press Calculate. First, the user clicks the button. Second, the browser fires a submit event. Third, JavaScript reads the fields. Fourth, it does the calculation. Fifth, it changes the page's text. Sixth, the browser draws the new text

What JavaScript really changes

result.textContent = ... doesn't touch your index.html file. When a browser loads a page, it builds a live model of it in memory, called the DOM (Document Object Model). JavaScript changes that model, and the browser redraws the screen to match.

We checked this after pressing the button: the live page contained 115.00, and the index.html file didn't. Refresh, and the result is gone, because the browser builds a new DOM from the file.

Take a layer away

Now that all three work, what happens if you remove one? We tried it:

What's missingHow it looksPress CalculateResult
NothingStyledTip: 15.00. Total: 115.00.Everything works
CSSPlain, like the first screenshotTip: 15.00. Total: 115.00.Ugly, and it still works
JavaScriptStyledThe page reloads, with no resultPretty, and it does nothing

This is why the layers are kept in separate files. Each has a single job, so you can change the design without touching the logic. It also shows the order of importance: the HTML has to be there for anything else to work.

One page, any screen

Your page will be seen on phones as well as computers. That's what the last rule in style.css is for. A media query applies CSS only under certain conditions, and here it changes the form to a single column on narrow screens. The viewport line in the HTML tells phones to use their real width.

At 560 pixels wide, the form has two columns. At 360, it has one:

The tip calculator at phone width. The labels now sit above their fields in a single column, and the result fits on the screen

Designing a page to adapt like this is called responsive design.

Built for everyone

Not everyone uses a mouse, and not everyone can see the screen. Some people use only a keyboard, and others use a screen reader, which reads the page aloud. Choosing the right HTML does most of the work, so we tested a good and a bad version of the same things side by side:

1<label for="labeled">Bill</label>
2<input id="labeled">
3<input id="unlabeled">
4<div id="fake" onclick="document.title = 'fake clicked'">Calculate</div>
5<button id="real" onclick="document.title = 'real clicked'">Calculate</button>

Pressing the Tab key moved through the page in this order:

labeled -> unlabeled -> real

The <div> was skipped, so a keyboard user can't reach that "button" at all. The real <button> works with no extra code. Chrome's accessibility tree, which is what a screen reader gets, shows the second difference:

textbox "Bill"
textbox ""
button "Calculate"

The field with a <label> has a name. The other is an unnamed box, and the fake button isn't a button at all. That's why the calculator uses <label>, <button> and <form>. Pressing Enter inside a field also submits the form, for free.

Your browser's tools

Every modern browser includes developer tools. Press F12 on any page, or Cmd+Option+I on a Mac, and try these panels:

  • Elements shows the live DOM. Press Calculate and watch the text change, and edit the CSS to try ideas.
  • Console shows errors from your scripts, and lets you run JavaScript. Try document.title.
  • Network shows every file the page requested.

For a frontend developer, these are the most-used tools after the code editor. The guide on what happens when you visit a website shows how to read the Network panel.

Beyond the three

Job listings mention plenty of other names, such as React, Vue, Angular, TypeScript, Tailwind and Vite. They can look like a fourth layer, and they aren't. Browsers still run HTML, CSS and JavaScript:

  • Frameworks such as React or Vue are JavaScript code that makes large interfaces easier to build.
  • TypeScript is compiled to JavaScript before it runs.
  • CSS tools such as Tailwind or Sass end up as ordinary CSS.
  • Build tools such as Vite prepare your files for the browser.

They all make the same three layers easier to work with at scale, so learning the basics first pays off with every one of them.

Have you thought about frontend?

Frontend work suits you if you like seeing your work on the screen right away, care about the details people touch, and want to think about how real users behave. A good path is the order of this guide: HTML, then CSS, then JavaScript, with a small project at each step. Learn a framework once plain JavaScript feels comfortable.

Your turn: practice

Try these before peeking at the answers.

Exercise 1: Which layer? (warm-up). Is each change done with HTML, CSS or JavaScript?

  1. Adding a third input field
  2. Making the button green
  3. Showing an error when the bill is empty
  4. Adding space between two fields
  5. Turning a line of text into a heading

Exercise 2: Predict it. What does each line print? Press F12 and run them in the Console to check.

1console.log("20" + 3);
2console.log("20" * 3);
3console.log("20" - 3);
4console.log(Number("20") + 3);

Exercise 3: Split the bill. Add a People field with the value 4, and change the message so that it also shows how much each person pays.

Exercise 4: Dark mode. Using only CSS, add dark colors for people whose device is set to dark mode. Hint: @media (prefers-color-scheme: dark).

Exercise 5: Live results (challenge). Remove the button and the form's submit listener. Update the result as the user types, using the input event, and remember the tip percentage after a refresh, using localStorage.

Quick quiz

  1. What's the job of each of the three frontend languages?
  2. What's the difference between a tag and an attribute?
  3. What's the difference between a static website and an application?
  4. Why did the first version give 10015?
  5. What does preventDefault do in the calculator?
  6. What's the DOM?
  7. What happens to the calculator without its CSS? And without its JavaScript?
  8. What's a media query for?
  9. Why use <button> and not a <div> with a click handler?
  10. Does a browser run React or TypeScript directly?

Answers

Exercise 1: (1) HTML. (2) CSS. (3) JavaScript. (4) CSS. (5) HTML.

Exercise 2: + joins text, and the other operators convert it to a number:

1203
260
317
423

Exercise 3: Add the field before the button in index.html:

<label for="people">People</label>
<input id="people" type="number" min="1" value="4">

Then replace the line that sets the message in script.js:

const people = Number(document.querySelector("#people").value);
const share = total / people;
result.textContent = `Tip: ${tip.toFixed(2)}. Total: ${total.toFixed(2)}. Each: ${share.toFixed(2)}.`;
Tip: 15.00. Total: 115.00. Each: 28.75.

Exercise 4: Add this to the end of style.css. No HTML or JavaScript changes:

1@media (prefers-color-scheme: dark) {
2  body {
3    background: #10243f;
4  }
5
6  main {
7    background: #1c2f4a;
8    color: white;
9  }
10
11  h1 {
12    color: #9cc4f5;
13  }
14}

Exercise 5: The input event fires on every change. Replace script.js with this, and remove the <button> from the HTML:

1const billInput = document.querySelector("#bill");
2const percentInput = document.querySelector("#percent");
3const result = document.querySelector("#result");
4
5function update() {
6  const bill = Number(billInput.value);
7  const tip = bill * Number(percentInput.value) / 100;
8  const total = bill + tip;
9  result.textContent = `Tip: ${tip.toFixed(2)}. Total: ${total.toFixed(2)}.`;
10  localStorage.setItem("percent", percentInput.value);
11}
12
13billInput.addEventListener("input", update);
14percentInput.addEventListener("input", update);
15
16const savedPercent = localStorage.getItem("percent");
17if (savedPercent !== null) {
18  percentInput.value = savedPercent;
19}
20
21update();

Typing 20 in the tip field gave this, and the 20 was still there after a refresh:

Tip: 20.00. Total: 120.00.

Quiz: (1) HTML defines the structure, CSS the style and JavaScript the interaction. (2) A tag marks an element, and an attribute adds information to it. (3) A static website only shows things, and an application responds to the user. (4) value is text, and + joins text. (5) It cancels the form's default action, which reloads the page. (6) The live model of the page in the browser's memory, which JavaScript changes. (7) Without CSS, it looks plain and still works. Without JavaScript, it looks fine and does nothing. (8) Applying CSS only under certain conditions, such as a narrow screen. (9) A <button> works with a keyboard and has a role for screen readers, with no extra code. (10) No. They end up as JavaScript.

Cheat sheet

I want to…Write
Link a stylesheet<link rel="stylesheet" href="style.css">
Link a script<script src="script.js"></script>
Name a field<label for="bill">Bill</label>
Pick an element in CSS#result { … }
Lay out in columnsdisplay: grid; grid-template-columns: 1fr 1fr;
Adapt to small screens@media (max-width: 400px) { … }
Fit phones<meta name="viewport" content="width=device-width, initial-scale=1">
Pick an element in JavaScriptdocument.querySelector("#result")
React to an eventform.addEventListener("submit", (event) => { … })
Stop the page from reloadingevent.preventDefault();
Read a number from a fieldNumber(billInput.value)
Change the text on the pageresult.textContent = "…";
Open the developer toolsPress F12, or Cmd+Option+I on a Mac

Wrapping up

Frontend development depends on three elements:

  • HTML defines the skeleton of the page, with tags and attributes.
  • CSS styles it, with colors, fonts, spacing and position.
  • JavaScript makes it dynamic, which turns a website into an application.
  • Events drive the interaction: read the page, calculate, and change it.
  • An input's value is text, so convert it before doing math.
  • JavaScript changes the DOM, and not your files.
  • Media queries adapt the page to any screen.
  • The right HTML makes a page accessible, to keyboards and screen readers.
  • Frameworks and TypeScript still end up as HTML, CSS and JavaScript.

Have you thought about following the path of a frontend developer?

Thanks for reading!

Know someone who would enjoy this? Send it their way.

More articles

Read next

Programming 4 min read

Ruby Blocks and Iterators: Write Your Own

Learn how Ruby methods receive blocks, pass values with yield, handle a missing block, and return an Enumerator. Build and run your own iterator.

nbkhope
Programming 6 min read

C++ std::vector: Size, Capacity, and Safe Growth

Learn how std::vector grows in C++: the difference between size and capacity, when reserve helps, why resize is different, and how growth can invalidate references.

nbkhope
Programming 5 min read

How to Handle Errors in Go: A Hands-On Guide

Learn Go's everyday error pattern with runnable examples: return an error, check it, add context with %w, and recognize a missing file with errors.Is.

nbkhope

Comments

Be kind and specific. Comments with links from new accounts appear after a moderator reviews them.