The browser stack in one sentence

  • HTML gives content meaning and structure.
  • CSS gives it layout, type, colour, and responsive behaviour.
  • JavaScript gives it behaviour and state.

Keep those roles distinct. A button should first be a real HTML <button>; CSS makes it look right; JavaScript attaches behaviour only where it is needed.

HTML: build meaning first

<main>
  <article>
    <h1>Internet watchdog</h1>
    <p>Records sustained WAN outages and sends an alert.</p>
    <button type="button" id="test-alert">Send test alert</button>
  </article>
</main>

Use semantic elements: header, nav, main, article, section, aside, footer, button, label, table. They help screen readers, search engines, keyboard users, and your future self.

Form essentials

<label for="schedule-time">Start time</label>
<input id="schedule-time" name="schedule-time" type="time" required>
<button type="submit">Save schedule</button>

Every form control needs an associated label. Use the native input type (email, number, date, time) before reaching for custom widgets.

CSS: layout with constraints, not coordinates

:root {
  --ink: #17221d;
  --surface: #f5f1e8;
  --accent: #c9684f;
  --space: 1rem;
}

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: calc(var(--space) * 1.25);
}

.card {
  padding: 1.5rem;
  border: 1px solid color-mix(in srgb, var(--ink) 18%, transparent);
}

Use Flexbox for one-dimensional alignment (a row or column) and Grid for two-dimensional layouts. Prefer gap, minmax, clamp, and logical constraints over fixed pixel positioning.

Responsive design

Design mobile-first, then enhance for larger screens:

.navigation { display: grid; gap: 1rem; }
@media (min-width: 48rem) {
  .navigation { display: flex; justify-content: space-between; }
}

Test a narrow viewport, keyboard navigation, long titles, and a slow/failed network—not just your own desktop.

JavaScript: add precise behaviour

<button type="button" id="menu-toggle" aria-expanded="false">Menu</button>
<nav id="menu" hidden>...</nav>
<script>
  const button = document.querySelector('#menu-toggle');
  const menu = document.querySelector('#menu');
  button.addEventListener('click', () => {
    const open = button.getAttribute('aria-expanded') === 'true';
    button.setAttribute('aria-expanded', String(!open));
    menu.hidden = open;
  });
</script>

Use const by default and let when reassignment is required. Avoid var. Keep selectors scoped and null-check elements when a script can run across multiple pages.

DOM, events, and state

The DOM is the browser’s object model of HTML. JavaScript can read it, change it, and listen for events such as click, input, or submit.

const input = document.querySelector('#library-search');
input.addEventListener('input', (event) => {
  const query = event.target.value.trim().toLowerCase();
  filterGuides(query);
});

Keep a clear source of truth. If state lives in a URL query parameter, update it deliberately with URL and history.replaceState; if it lives on the server, refresh it from the server rather than maintaining an inconsistent copy in the UI.

Fetching APIs safely

async function getStatus() {
  const response = await fetch('/api/status');
  if (!response.ok) throw new Error(`Status request failed: ${response.status}`);
  return response.json();
}

Always handle loading and failure states in real interfaces. Do not assume fetch() means success: it only rejects on network-level errors; HTTP 404/500 still need response.ok checks.

Accessibility is quality, not an add-on

  • Use real buttons for actions, real links for navigation.
  • Keep focus visible: never remove outlines without a better replacement.
  • Ensure text contrast is high enough to read.
  • Do not convey essential status using colour alone.
  • Give images useful alt text; use empty alt="" only for purely decorative images.
  • Make every primary workflow usable with keyboard alone.

Run the browser accessibility tree/inspector and try Tab/Enter yourself.

Astro: a good fit for the KB

Astro renders content to fast static HTML by default. Use .astro components for data/layout, Markdown for guides, and small inline/client JavaScript only for behaviour such as the KB’s live search and category filters.

---
const title = 'Hello';
---
<h1>{title}</h1>

For this KB, remember that static pages are built ahead of time: URL query filtering must run in the browser when visitors arrive, not only during the Astro build.

Useful browser debugging

console.log({ state, response });
console.table(items);

In DevTools:

  1. Console: syntax errors and logs.
  2. Network: API request URL, status, response, timing.
  3. Elements: final DOM and CSS rules actually applied.
  4. Application: local storage/cookies/cache.
  5. Accessibility: names, roles, keyboard/focus issues.

Common traps

TrapBetter practice
Clickable <div>Use <button> or <a>
One giant CSS file with magic pixel positionsComponents, Grid/Flexbox, tokens
JavaScript before HTML worksProgressive enhancement; native HTML first
Ignoring HTTP response statusCheck response.ok
innerHTML with untrusted contentUse text nodes / safe rendering
Desktop-only testingTest mobile, keyboard, zoom, long text
Copying random code snippetsUnderstand API/version/context first

A first useful project

Build a single-page “Home Lab Status” card:

  1. Static HTML for system name and status areas.
  2. CSS Grid layout that collapses well on mobile.
  3. JavaScript fetches a safe status endpoint or a local JSON fixture.
  4. A refresh button, loading text, and error message.
  5. Test keyboard navigation and a simulated failed request.