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
alttext; use emptyalt=""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:
- Console: syntax errors and logs.
- Network: API request URL, status, response, timing.
- Elements: final DOM and CSS rules actually applied.
- Application: local storage/cookies/cache.
- Accessibility: names, roles, keyboard/focus issues.
Common traps
| Trap | Better practice |
|---|---|
Clickable <div> | Use <button> or <a> |
| One giant CSS file with magic pixel positions | Components, Grid/Flexbox, tokens |
| JavaScript before HTML works | Progressive enhancement; native HTML first |
| Ignoring HTTP response status | Check response.ok |
innerHTML with untrusted content | Use text nodes / safe rendering |
| Desktop-only testing | Test mobile, keyboard, zoom, long text |
| Copying random code snippets | Understand API/version/context first |
A first useful project
Build a single-page “Home Lab Status” card:
- Static HTML for system name and status areas.
- CSS Grid layout that collapses well on mobile.
- JavaScript fetches a safe status endpoint or a local JSON fixture.
- A refresh button, loading text, and error message.
- Test keyboard navigation and a simulated failed request.