Skip to content
AgileLogics
7 min read

Connecting HTML and JavaScript

Prerequisites: Connecting HTML and CSS

JavaScript makes pages interactive. It reaches the page through the DOM — the tree the browser built from your HTML.

Loading scripts

<head>
  <meta charset="utf-8">
  <title>Scripts</title>
  <script src="/js/analytics.js" async></script>
  <script src="/js/app.js" defer></script>
  <script type="module" src="/js/widgets.js"></script>
</head>
Script Downloads Runs
<script src> (plain) Pauses parsing Immediately — blocks the page
async In parallel As soon as it arrives — order not guaranteed
defer In parallel After the HTML is parsed, in document order
type="module" In parallel Deferred by default; supports import

Use defer (or modules) for your app code and async for independent scripts like analytics. A plain script in the head delays everything below it.

Scripts run in order with parsing

A plain inline script sees only the HTML above it:

<p id="first">First</p>
<script>
  console.log("first:", document.getElementById("first") !== null);
  console.log("second:", document.getElementById("second") !== null);
  document.addEventListener("DOMContentLoaded", () => {
    console.log("after parsing, second:", document.getElementById("second") !== null);
  });
</script>
<p id="second">Second</p>
first: true
second: false
after parsing, second: true

defer and modules avoid this problem — they run after the whole document is parsed.

Finding and changing elements

<ul id="list">
  <li>Rye</li>
  <li class="special">Spelt</li>
</ul>
<p id="count"></p>
<script>
  const list = document.querySelector("#list");
  const items = list.querySelectorAll("li");
  console.log(items.length, list.querySelector(".special").textContent);

  const li = document.createElement("li");
  li.textContent = "Sourdough <fresh>";
  list.append(li);
  console.log(list.lastElementChild.outerHTML);

  document.getElementById("count").textContent = `${list.children.length} breads`;
  console.log(document.getElementById("count").textContent);
</script>
2 Spelt
<li>Sourdough &lt;fresh&gt;</li>
3 breads

Setting textContent inserts text, escaping < — the safe way to show user data. innerHTML parses HTML and is dangerous with untrusted input (Level 3, Sanitizing User Content).

Events

<button type="button" id="like" aria-pressed="false">Like</button>
<script>
  const button = document.getElementById("like");
  button.addEventListener("click", () => {
    const pressed = button.getAttribute("aria-pressed") === "true";
    button.setAttribute("aria-pressed", String(!pressed));
    console.log("pressed:", button.getAttribute("aria-pressed"));
  });
  button.click();
  button.click();
</script>
pressed: true
pressed: false

Prefer addEventListener over inline handler attributes like onclick="..." — they mix behavior into markup and are blocked by a strict Content Security Policy.

Progressive enhancement

Build the page so the HTML works on its own — real links, real forms that submit to the server — then let JavaScript improve it. If a script fails to load (it happens: flaky networks, blockers, errors), people can still use the page.

noscript content is shown only when scripting is disabled:

<noscript><p>The live map needs JavaScript. <a href="/directions">Read the directions instead</a>.</p></noscript>

Related developer tools