Linking CSS and JavaScript

Introduction

HTML describes structure; CSS styles it and JavaScript adds behavior. This chapter shows how to link external stylesheets and scripts, when to use inline or embedded code, and why separating concerns keeps projects maintainable. You will also see why heavy use of HTML event attributes is discouraged.

Prerequisites

Three Ways to Add CSS

MethodWhereBest for
External file<link rel="stylesheet">Real projects
Internal<style> in <head>Small demos, email-like constraints
Inlinestyle attribute on one elementRare exceptions, quick prototypes
html
<head>
  <meta charset="UTF-8">
  <title>Styled Page</title>
  <link rel="stylesheet" href="css/styles.css">
</head>

Project layout:

text
project/
├── index.html
└── css/
    └── styles.css

styles.css:

css
body {
  font-family: system-ui, sans-serif;
  line-height: 1.6;
  margin: 2rem;
}
 
h1 {
  color: #1e293b;
}

Benefits:

  • one file styles many pages
  • browser caches CSS across pages
  • HTML stays readable
  • designers and developers can work on CSS independently

Internal CSS

html
<head>
  <style>
    main {
      max-width: 40rem;
    }
  </style>
</head>

Fine for tiny examples. Avoid large blocks of CSS in HTML for production sites.

Inline Styles

html
<p style="color: #dc2626;">This paragraph is red.</p>

Hard to maintain and cannot reuse rules. Use sparingly—for example, email HTML where external stylesheets are limited.

Tip

Structure in HTML, Presentation in CSS

Choose elements for meaning (article, nav, button). Use CSS for layout, color, and typography.

class and id for Styling Hooks

HTML provides hooks CSS (and JavaScript) can target:

html
<article class="card featured">
  <h2 id="pricing">Pricing</h2>
  <p class="muted">Plans for every team.</p>
</article>
AttributePurpose
classReusable group (many elements)
idUnique identifier (one per page)

CSS examples (preview only):

css
.card {
  border: 1px solid #e2e8f0;
  padding: 1rem;
}
 
.featured {
  border-color: #2563eb;
}
 
#pricing {
  scroll-margin-top: 2rem;
}

Do not choose heading levels for size—use classes and CSS instead.

Linking JavaScript

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

Place in <head> with defer, or before </body> without defer.

js/app.js:

js
const button = document.querySelector("#greet");
button.addEventListener("click", () => {
  alert("Hello from JavaScript");
});

HTML:

html
<button type="button" id="greet">Say hello</button>
<script src="js/app.js" defer></script>

Internal Script

html
<script>
  console.log("Page loaded");
</script>

Use for very small demos. Prefer external files for real apps.

defer vs async

html
<script src="js/analytics.js" async></script>
<script src="js/app.js" defer></script>
AttributeDownloadExecuteOrder
(none)Blocks parsingImmediatelyBlocking
deferParallelAfter HTML parsedPreserved
asyncParallelAs soon as readyNot guaranteed

Rules of thumb:

  • App logic that needs the DOM: defer
  • Independent scripts (ads, analytics): async if order does not matter
  • Avoid blocking scripts in <head> without defer/async
html
<head>
  <link rel="stylesheet" href="css/styles.css">
  <script src="js/app.js" defer></script>
</head>

Script Placement: Head vs End of Body

With defer in head — modern default:

html
<head>
  <script src="js/app.js" defer></script>
</head>
<body>...</body>

Without defer — classic pattern at end of body:

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

Both work when the DOM exists before the script runs. Pick one convention per project.

Avoid Heavy HTML Event Attributes

This works:

html
<button type="button" onclick="alert('Hi')">Click</button>

Problems at scale:

  • mixes behavior into markup
  • hard to test and maintain
  • Content Security Policy may block inline handlers
  • one function name per attribute

Prefer:

html
<button type="button" id="save">Save</button>
<script src="js/app.js" defer></script>
js
document.querySelector("#save").addEventListener("click", () => {
  // save logic
});

Keep HTML declarative; keep behavior in JS files.

DOM Concept (Bridge to JavaScript)

The browser parses HTML into a DOM tree. JavaScript can query and change it:

html
<p id="status">Waiting...</p>
<button type="button" id="load">Load data</button>
js
const status = document.querySelector("#status");
const loadBtn = document.querySelector("#load");
 
loadBtn.addEventListener("click", () => {
  status.textContent = "Loaded.";
});

HTML provides the elements; JavaScript responds to events. Full DOM coverage lives in DOM manipulation in JavaScript.

Module Scripts (Awareness)

Modern projects may use ES modules:

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

Modules are deferred by default. They use import/export. Static HTML learning often starts with classic scripts; frameworks add bundlers later.

Putting It Together

Mini Exercise

  1. Create css/styles.css and link it from index.html.
  2. Style .card and one h1 with external CSS only.
  3. Add js/app.js with defer that logs a message on load.
  4. Wire one button with addEventListener instead of onclick.
  5. Confirm in DevTools Network that CSS and JS load with 200 status.

FAQ

Can I put CSS in the body?

<link rel="stylesheet"> belongs in <head>. <style> in body is invalid in strict parsing though browsers may recover.

Multiple CSS files?

Yes. Order matters—later rules override earlier ones (same specificity).

html
<link rel="stylesheet" href="css/base.css">
<link rel="stylesheet" href="css/theme.css">

Should I use jQuery in new HTML pages?

Modern sites use vanilla JS or frameworks. This course uses plain JS for clarity.

Does defer work on inline scripts?

No. defer applies to external src scripts.

What comes next?

Data attributes — store custom metadata on elements for CSS and JavaScript.