Every website you visit — from a simple blog to a complex web application — is built from the same three technologies: HTML, CSS, and JavaScript. They are not interchangeable; each one has a distinct job, and understanding the division of responsibility between them is one of the most important foundations in web development.

The three layers

Think of a webpage as a building. HTML is the concrete and steel — the raw structure that holds everything up. CSS is the interior design — the paint, the furniture, the layout. JavaScript is the plumbing and electrics — the systems that make things actually work when you interact with them.

Layer 1
HTML
Structure and content — headings, paragraphs, buttons, images, links.
Layer 2
CSS
Visual style — colours, fonts, spacing, layout, animations.
Layer 3
JavaScript
Behaviour and interactivity — responding to clicks, fetching data, updating content.

How the browser processes them

When you navigate to a URL, the browser fetches the HTML file first. It reads through it top to bottom, building a tree of elements called the DOM (Document Object Model). The DOM is the browser's internal representation of the page — a structured map of every element and its relationships.

As the browser parses the HTML it encounters references to CSS files and JavaScript files. CSS is downloaded and used to build a second internal structure called the CSSOM (CSS Object Model), which describes the styles that apply to each element. The browser merges the DOM and CSSOM into a render tree, calculates the position and size of every element (layout), and then paints pixels to screen.

JavaScript runs after the DOM is available (or immediately if placed in the <head> with special attributes). It can read and modify both the DOM and the applied styles at any time, which is how interactive behaviour — updating content without a page reload, responding to user input, animating elements — is achieved.

The critical path: Parse HTML → Build DOM → Apply CSS → Run JS → Render. Blocking any step (for example, a large synchronous script in the <head>) delays everything that follows it.

Why separation of concerns matters

Keeping HTML, CSS, and JavaScript in separate files is a principle called separation of concerns. It matters for several practical reasons:

A practical example: a button that changes colour on click

This is the same feature built with each layer doing exactly its job. Notice how each file is completely independent and could be swapped out without touching the others.

HTML — the structure

The HTML defines that a button exists and gives it an identity. It says nothing about what colour the button is or what happens when it is clicked.

<button id="colour-btn" class="my-button">Click me</button>

CSS — the style

The CSS describes how the button looks in its default state, and how it looks when the JavaScript adds an active class to it. The CSS knows nothing about JavaScript — it just defines rules for class names.

.my-button {
  background: #2563eb;
  color: #fff;
  padding: 0.75rem 1.5rem;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background 0.3s;
}

.my-button.active {
  background: #16a34a;
}

JavaScript — the behaviour

The JavaScript listens for a click event and toggles the active class on the button. It does not set any colours directly — it delegates that entirely to CSS.

const btn = document.getElementById('colour-btn');

btn.addEventListener('click', () => {
  btn.classList.toggle('active');
});

This is a deliberate design choice: JavaScript manipulates state (active or not active) and CSS responds to that state with the appropriate visual treatment. Mixing colour values directly into JavaScript is harder to maintain and defeats the purpose of CSS.

Inline vs external: when to use each

Both CSS and JavaScript can be written inline inside the HTML file or in separate external files. Each approach has legitimate uses.

Approach When to use it
<style> in HTML Single-page prototypes, email HTML, critical above-the-fold styles to avoid a render-blocking request.
External .css file Any real project. Enables caching, reuse across pages, and clean separation of concerns.
<script> inline in HTML Small snippets, quick experiments, or tiny config objects passed from server to client.
External .js file Any real project. Load with defer or async to avoid blocking rendering.
Tip: Add defer to external script tags (<script src="app.js" defer>) so the browser downloads the script without blocking HTML parsing, and runs it after the DOM is ready. This removes the need to wrap everything in DOMContentLoaded handlers.

Why code playgrounds are useful for experimenting

Understanding how HTML, CSS, and JavaScript interact is much faster when you can see the result instantly. Code playgrounds like CodePen and the SmartiTools Code Playground give you three panels — one for each language — and render the output live as you type.

This tight feedback loop is ideal for learning. You can change a CSS property and immediately see the visual effect, or add a line of JavaScript and watch the behaviour update in real time, without needing to set up a local development environment, manage files, or reload a browser tab manually.

Playgrounds are also excellent for isolating problems. When debugging a tricky interaction between a CSS transition and a JavaScript class toggle, stripping everything back to a minimal example in a playground is far faster than hunting through a full project.

Try it in the Code Playground

Write HTML, CSS, and JavaScript side by side and see the result live — no setup required.

Open Code Playground