Web cheatsheet98 modules13 phasesLive previews and full code
How websites work,from one tag to a full page
Start with a single HTML element, learn the three ways to add CSS and JavaScript, build media cards, navbars and heroes, assemble a full single page site, then see why Tailwind, Bootstrap, React and Next.js came up and ship it all in Docker.
By Shree Kumar Sharma, with Claude Design, for Backend Engineering. Progress is saved in this browser only.
A catalog of every keyword in this guide. Each entry says what the word means in plain language, what it means technically, and what it is like in everyday life, then links to the module that teaches it.
In detail
Use this module as a reference rather than a lesson. Filter by kind to see only HTML, CSS, JavaScript or tooling words, or type to find one. Tags, properties and functions in the code examples across this page link back to the module that explains them.
Client and server
Web
In plain words
Your browser asks, a computer somewhere answers.
Technically
The client sends HTTP requests; the server returns responses with status codes, headers and a body.
Think of it as
A customer ordering at a counter and the kitchen sending the food out.
A browser asks a server for a file over the internet, the server answers, and the browser turns the answer into the page you see. Everything else in web development builds on that loop.
In detail
The client is the browser on your device. The server is a computer that stores and sends files or builds them on demand. They talk using HTTP, find each other using DNS and IP addresses, and keep the conversation private with TLS. A single page usually needs dozens of requests: the HTML first, then the CSS, JavaScript, images and fonts it references.
A URL tells the browser which protocol, which server and which file to ask for. DNS turns the human friendly domain into the numeric IP address of that server.
In detail
Read a URL left to right: scheme (https), host (example.com), optional port, path (/blog/post), query (?page=2) and fragment (#comments). The fragment never reaches the server; the browser uses it to scroll. DNS lookups are cached at several levels, which is why a changed record can take time to reach everyone.
HTTP is the language browsers and servers speak. A request has a method, a path and headers; a response has a status code, headers and a body.
In detail
GET reads, POST creates, PUT and PATCH update, DELETE removes. Status codes group by their first digit: 2xx success, 3xx redirect, 4xx the client made a mistake, 5xx the server failed. Headers carry everything else: content type, caching rules, cookies and security policies.
request.httpShell
GET /blog/post HTTP/1.1Host: example.comAccept: text/html
The browser parses HTML into the DOM, CSS into the CSSOM, combines them into a render tree, calculates layout, then paints pixels and composites layers.
In detail
This sequence is called the critical rendering path. CSS blocks rendering because the browser needs styles before it can paint; scripts without defer block parsing because they might change the document. Understanding the path explains most performance advice: load CSS early, defer scripts, and avoid forcing layout repeatedly from JavaScript.
Every browser ships developer tools: an Elements panel to inspect and edit the DOM and CSS live, a Console for JavaScript, a Network panel for requests, and panels for performance and accessibility.
In detail
Press F12 or Ctrl Shift I. Edit a style in Elements and see it instantly, read errors in Console, and filter Network by type to see what loaded, how big it was and how long it took. Lighthouse audits performance, accessibility and SEO in one click. Device mode previews phones and slow networks.
console-tricks.jsJavaScript
console.table([{ name: "Ada", role: "admin" }]);$0.style.outline = "2px solid red"; // $0 is the selected element
// Pretty print arrays of objects as a tableconsole.table([ { name: "Ada", role: "admin" }, { name: "Linus", role: "editor" },]);// Group related logsconsole.group("Cart");console.log("items", 3);console.warn("coupon expired");console.groupEnd();// Measure how long something takesconsole.time("render");document.querySelectorAll("li").forEach((li) => li.classList.add("seen"));console.timeEnd("render");// In the Elements panel, $0 is the element you selected// $0.style.outline = "2px solid red";
Why it matters console.table and console.time turn guessing into reading. They exist in every modern browser.
A code editor, a folder with index.html, styles.css and script.js, and a live preview server are all you need to start building.
In detail
VS Code with the Live Server or Live Preview extension reloads the page on every save. Keep files lowercase with hyphens, put images in an assets folder, and always open the page through a local server rather than the file system, because modules and fetch do not work from file URLs.
my-first-site
my-first-site
index.htmlhome page, the entry point
about.htmla second page
css
style.cssexternal stylesheet
js
app.jsloaded with defer
assets
hero.webpoptimised image
logo.svgscalable logo
favicon.ico
README.mdwhat the project is
Lowercase names with hyphens avoid broken links on case sensitive servers.
terminalShell
mkdir my-site && cd my-sitetouch index.html styles.css script.jsnpx serve .
# create a project folder with the three core filesmkdir my-site && cd my-sitemkdir -p assets/images assets/fontstouch index.html styles.css script.js# serve it locally (any one of these works)npx serve .python3 -m http.server 8080# open http://localhost:3000 (serve) or http://localhost:8080 (python)
Why it matters A local server mirrors how a real host serves files, so relative paths, modules and fetch behave correctly.
02
Phase 02, modules 07 to 19
The skeleton
HTML foundations
Documents, elements, media, forms and the semantic structure every page is built on.
Every page starts with a doctype, an html element with a language, a head for information about the page, and a body for what people see.
In detail
The doctype switches the browser to standards mode. The head holds the character set, viewport, title, description and links to CSS. The body holds content. Indent nested elements, close every element that needs closing, and validate with the W3C validator when something looks wrong.
An element is an opening tag, content and a closing tag. Attributes inside the opening tag add information such as an id, a class, a link target or alternative text.
In detail
Void elements such as img, input, br and meta have no closing tag. Elements nest like boxes inside boxes and must close in reverse order. Global attributes work on any element: id must be unique, class can repeat, hidden hides, lang sets language, and data-* stores your own values for scripts.
elements.htmlHTML
<pclass="note"id="intro"data-level="beginner">Read <ahref="/guide">the guide</a>.</p><imgsrc="cat.jpg"alt="A cat asleep on a keyboard"/>
<!-- element = opening tag + content + closing tag --><pclass="note"id="intro"data-level="beginner"> Start with <ahref="/guide"title="Open the guide">the guide</a>.</p><!-- void elements have no closing tag --><imgsrc="cat.jpg"alt="A cat asleep on a keyboard"width="640"height="420"/><br/><inputtype="email"name="email" required /><!-- boolean attributes are on when present --><details open><summary>More info</summary><p hidden>This paragraph is hidden until a script reveals it.</p></details><!-- data attributes are read in JavaScript as element.dataset.level -->
Why it matters data-* attributes are the clean bridge between HTML and JavaScript. Scripts read them with element.dataset.
Headings h1 to h6 build an outline, paragraphs hold prose, and inline elements such as strong, em, code, abbr and time add meaning that browsers, search engines and screen readers understand.
In detail
Use one h1 per page and never skip levels just for size; style with CSS instead. strong means importance, em means emphasis, b and i are only visual. blockquote and cite quote sources, pre keeps whitespace, and time carries a machine readable date.
<article><h2>Why semantics matter</h2><p> Search engines read <strong>meaning</strong>, not just <em>looks</em>. The <abbrtitle="HyperText Markup Language">HTML</abbr> you choose tells them what matters.</p><h3>A quote worth keeping</h3><blockquotecite="https://www.w3.org/"><p>The power of the Web is in its universality.</p></blockquote><p>Tim Berners-Lee, <cite>W3C</cite></p><h3>Code and dates</h3><p>Run <code>npm run build</code> before you deploy.</p><p>Published <timedatetime="2026-10-04">4 October 2026</time>.</p><pre> keeps its spacing</pre></article>
Why it matters Screen reader users jump between headings to scan a page. A skipped level breaks that outline.
The anchor element links to other pages, to sections of the same page, to email and phone, and to downloads.
In detail
Use absolute URLs for other sites, relative URLs inside your site, and #id to jump within a page. Add rel="noopener" with target="_blank" so the new tab cannot control yours. Write link text that makes sense alone; click here tells nobody where they are going.
links.htmlHTML
<ahref="/pricing">See pricing</a><ahref="#faq">Jump to the FAQ</a><ahref="https://developer.mozilla.org"target="_blank"rel="noopener">MDN</a>
<navaria-label="Primary"><ahref="/">Home</a><ahref="/pricing"aria-current="page">Pricing</a><ahref="#faq">Jump to the FAQ</a></nav><!-- external link in a new tab --><ahref="https://developer.mozilla.org"target="_blank"rel="noopener noreferrer">MDN Web Docs</a><!-- email, phone and download --><ahref="mailto:hello@example.com?subject=Hi">Email us</a><ahref="tel:+911234567890">Call us</a><ahref="/files/brochure.pdf" download>Download the brochure (PDF, 2 MB)</a><sectionid="faq"><h2>FAQ</h2></section>
Why it matters aria-current="page" tells assistive technology which link is the current page, and gives CSS a hook to style it.
The img element shows an image with alternative text. srcset, sizes and picture let the browser choose the right file for each screen and format.
In detail
Always write alt text that describes purpose, or alt="" for decoration. Set width and height so the page does not jump while loading. Serve modern formats such as AVIF and WebP through picture with a JPEG fallback, and add loading="lazy" for images below the fold.
<figureclass="shot"><picture><sourcesrcset="lake-800.avif 800w, lake-1600.avif 1600w"type="image/avif"/><sourcesrcset="lake-800.webp 800w, lake-1600.webp 1600w"type="image/webp"/><imgsrc="lake-800.jpg"srcset="lake-800.jpg 800w, lake-1600.jpg 1600w"sizes="(max-width: 700px) 100vw, 700px"alt="A calm lake at sunrise with mist over the water"width="800"height="533"loading="lazy"decoding="async"/></picture><figcaption>Sunrise over the lake, shot on a phone.</figcaption></figure>
Why it matters sizes tells the browser how wide the image will display, so it can pick the smallest file from srcset that still looks sharp.
The video and audio elements play media natively with controls, captions and multiple sources. iframe embeds other pages such as maps and players.
In detail
Give video a poster, provide WebM and MP4 sources, and add a track element with captions. Autoplay only works muted. For iframes set a title for accessibility, loading="lazy" and a sandbox when the content is not yours.
<video controls playsinline width="640"height="360"poster="intro-poster.jpg"preload="metadata"><sourcesrc="intro.webm"type="video/webm"/><sourcesrc="intro.mp4"type="video/mp4"/><trackkind="captions"src="intro.en.vtt"srclang="en"label="English" default /> Your browser does not support video. <ahref="intro.mp4">Download it</a>.</video><audio controls src="podcast-episode-1.mp3"><ahref="podcast-episode-1.mp3">Download the episode</a></audio><iframetitle="Office location on a map"src="https://www.openstreetmap.org/export/embed.html?bbox=77.2,28.6,77.3,28.7"width="600"height="400"loading="lazy"sandbox="allow-scripts allow-same-origin"></iframe>
Why it matters preload="metadata" loads only the duration and first frame, so a page with several videos stays fast.
Unordered lists group items without order, ordered lists number them, and description lists pair terms with explanations.
In detail
Navigation menus, card grids and feature lists are usually ul elements styled with CSS, because a screen reader announces how many items there are. ol accepts start and reversed. dl suits glossaries and key value details.
Tables present data in rows and columns with headers, a caption and optional groups. They are for data, never for page layout.
In detail
Use thead, tbody and tfoot, mark header cells with th and a scope, and add a caption that describes the table. Two tone rows with nth-child make long tables easy to follow, and wrapping the table in a scrollable container keeps it usable on phones.
Forms collect input with labelled controls and send it to a server or to JavaScript. HTML gives you text, email, number, date, checkbox, radio, select, file and more for free.
In detail
Every input needs a label linked by for and id. Group related controls with fieldset and legend. The type attribute changes the mobile keyboard and adds built in checks, and autocomplete hints let browsers fill details for people.
HTML validates many inputs without JavaScript: required, type, min, max, minlength, maxlength and pattern. CSS can style valid and invalid states.
In detail
Native validation blocks submit and shows a message in the person's language. Style errors with :user-invalid so fields only turn red after someone interacts. Server side validation is still required, because anyone can bypass the browser.
header, nav, main, article, section, aside and footer describe the regions of a page. They look like div by default but carry meaning.
In detail
Landmarks let screen reader users jump to the main content or navigation instantly, and they help search engines understand structure. Use one main per page, article for self contained content such as a blog post, section for a themed group with a heading, and div only when no meaningful element fits.
The head carries metadata for search engines and social apps: title, description, canonical URL, Open Graph and Twitter cards, and structured data in JSON-LD.
In detail
Write a unique title under about sixty characters and a description under about one hundred sixty. Open Graph controls the preview card when a link is shared. JSON-LD describes the page as an article, product or FAQ so search results can show rich snippets.
head-seo.htmlHTML
<title>Learn CSS Grid in 10 minutes | Web Guide</title><metaname="description"content="A visual, beginner friendly tour of CSS Grid."/><metaproperty="og:image"content="https://example.com/og/grid.png"/>
<head><title>Learn CSS Grid in 10 minutes | Web Guide</title><metaname="description"content="A visual, beginner friendly tour of CSS Grid with copy ready examples."/><linkrel="canonical"href="https://example.com/css-grid"/><metaproperty="og:type"content="article"/><metaproperty="og:title"content="Learn CSS Grid in 10 minutes"/><metaproperty="og:description"content="A visual tour of CSS Grid."/><metaproperty="og:image"content="https://example.com/og/grid.png"/><metaname="twitter:card"content="summary_large_image"/><scripttype="application/ld+json"> {"@context": "https://schema.org","@type": "Article","headline": "Learn CSS Grid in 10 minutes","author": { "@type": "Person", "name": "Ada Lovelace" },"datePublished": "2026-10-04" }</script></head>
Why it matters canonical tells search engines which URL is the original when the same page is reachable at several addresses.
Accessible pages work for keyboard users, screen reader users, people who zoom, and people with low vision or motion sensitivity. Most of it comes from correct HTML.
In detail
Use real buttons and links, label every control, keep visible focus, give images alt text, and keep text contrast at least 4.5 to 1. Reach for ARIA only when no native element exists, and respect prefers-reduced-motion for animation.
a11y.htmlHTML
<buttontype="button"aria-expanded="false"aria-controls="menu">Menu</button><imgsrc="chart.png"alt="Sales rose 20 percent from June to July"/>
<!-- a real button, not a clickable div --><buttontype="button"class="menu-toggle"aria-expanded="false"aria-controls="menu"><svgaria-hidden="true"width="20"height="20"><usehref="#icon-menu"/></svg><spanclass="visually-hidden">Open menu</span></button><ulid="menu" hidden>...</ul><!-- alt describes purpose, not pixels --><imgsrc="chart.png"alt="Sales rose 20 percent from June to July"/><style> .visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0000); white-space: nowrap; } :focus-visible { outline: 3px solid #1d63ed; outline-offset: 2px; } @media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }</style>
Why it matters aria-expanded tells assistive technology whether the menu is open. Update it in JavaScript whenever the menu toggles.
03
Phase 03, modules 20 to 30
The skin
CSS foundations
Three ways to add styles, how the cascade decides, and the box every element lives in.
CSS can live in a style attribute on one element, in a style element in the head, or in an external stylesheet linked with link. External files are the default for real sites.
In detail
Inline styles win almost every cascade fight and cannot use media queries or hover, so keep them for values set by JavaScript. Internal styles suit single page demos and critical CSS. External stylesheets are cached by the browser and shared across pages, which makes them fastest after the first visit.
<!-- Inline: one element, highest priority, no media queries or :hover --><pstyle="color: #1d63ed; font-weight: 600">Inline style on one paragraph</p><!-- Good use: a value computed by JavaScript --><divclass="progress"><spanclass="bar"style="width: 64%"></span></div>
Why it matters Inline styles are hard to override and impossible to reuse. Keep them for dynamic values.
Selectors choose which elements a rule styles: by type, class, id, attribute, relationship and state.
In detail
Class selectors are the workhorse. Combinators select by relationship: a space for any descendant, > for direct children, + for the next sibling and ~ for later siblings. Attribute selectors match patterns such as links ending in .pdf. :is() and :where() group selectors, and :where() adds no specificity.
When several rules target the same element, the cascade decides: origin and importance first, then cascade layers, then specificity, then source order.
In detail
Specificity is counted as ids, classes and attributes, then types: #nav .link scores 1, 1, 0. Later rules win ties. Some properties such as color and font inherit from parents; others such as margin and border do not. @layer lets you order whole groups of styles so a framework's rules never beat yours by accident.
Every element is a rectangle made of content, padding, border and margin. box-sizing decides whether width includes padding and border.
In detail
With the default content-box, width: 200px plus 20px padding makes a 240px wide box. Setting box-sizing: border-box everywhere makes widths predictable. Vertical margins between blocks collapse into the larger one, which surprises everyone once; gap in flex and grid avoids it.
margin
border
padding
contentwidth x height
With box-sizing: border-box, width includes padding and border, so a 200px box stays 200px.
/* A sane default for every element */*,*::before,*::after {box-sizing: border-box;}.card {width: 300px; /* includes padding and border with border-box */padding: 20px24px; /* top-bottom, left-right */border: 2px solid #1d63ed;border-radius: 14px;margin: 16px auto; /* auto left and right centres a block */outline: 2px dashed transparent; /* outline does not take space */}.card:focus-within {outline-color: #1d63ed;}
Why it matters outline draws outside the border without changing layout, which is why it is the right tool for focus rings.
CSS has absolute units such as px and relative units such as rem, em, %, vw, vh, dvh, ch and fr. Relative units make layouts that respect user settings and screen sizes.
In detail
Use rem for font sizes and spacing so everything scales when someone increases their default font. Use % and fr for fluid widths, ch to limit line length, and dvh for full height sections on mobile where browser bars come and go. min(), max() and clamp() combine units into responsive values.
html { font-size: 100%; } /* respect the user's default, usually 16px */h1 { font-size: clamp(2rem, 1.2rem + 3vw, 4rem); } /* fluid but bounded */p { font-size: 1rem; margin-block: 01em; } /* em is relative to this element */.prose { max-width: 65ch; } /* about 65 characters per line */.sidebar { width: min(320px, 100%); } /* never wider than its container */.hero { min-height: 100dvh; } /* full height that respects mobile browser bars */.thumb { aspect-ratio: 16 / 9; width: 100%; }.layout {display: grid;grid-template-columns: 1fr3fr; /* fr shares leftover space */}
Why it matters clamp(min, preferred, max) replaces whole sets of media queries for font sizes and spacing.
Colours can be written as hex, rgb, hsl or modern oklch, and fonts are set with font-family stacks, web fonts and properties for size, weight, line height and spacing.
In detail
oklch gives perceptually even colour steps, and color-mix() derives tints from a brand colour. Load web fonts with @font-face and font-display: swap so text shows immediately. Good body text has a line height around 1.5 and a comfortable measure.
Normal flow stacks block elements vertically and lines inline elements up like words. The display property changes that: block, inline, inline-block, flex, grid, none and contents.
In detail
Block boxes take the full width and start on a new line; inline boxes ignore width and vertical margin. inline-block sits in a line but accepts size. display: none removes an element from layout and the accessibility tree, while visibility: hidden only hides it.
span.tag {display: inline-block; /* flows like text but accepts padding and width */padding: 2px8px;border-radius: 999px;background: #eef4ff;}a.button {display: block; /* full width row */text-align: center;}.visually-gone { display: none; } /* removed from layout and screen readers */.invisible { visibility: hidden; } /* still takes up space */.wrapper-only { display: contents; } /* children act as if the wrapper is absent */
Why it matters display: contents is handy for grid and flex layouts, but test it with a screen reader before using it on buttons or lists.
position: relative, absolute, fixed and sticky move elements out of normal flow or pin them, and z-index decides which overlapping element is on top.
In detail
absolute positions relative to the nearest positioned ancestor, so give the parent position: relative. fixed sticks to the viewport, sticky sticks within its scroll container once you scroll past it. z-index only compares elements in the same stacking context; transforms and opacity create new contexts, which explains most z-index mysteries.
Pseudo classes style states such as :hover, :focus-visible, :checked and :nth-child. Pseudo elements such as ::before, ::after and ::placeholder style parts that are not elements.
In detail
Use :focus-visible for keyboard focus rings, :nth-child(even) for two tone rows, :not() to exclude, and :empty to hide empty containers. ::before and ::after need a content property and are perfect for decorative icons, quotes and overlays.
Custom properties store values such as colours and spacing under names like --brand, and var() reads them. They cascade and inherit, so changing one value at the top updates everything.
In detail
Define design tokens on :root, override them on a component or under a theme attribute, and update them from JavaScript with style.setProperty. Unlike Sass variables they exist at runtime, which is what makes live theming and dark mode simple.
:root {--brand: #1d63ed;--brand-ink: #ffffff;--surface: #ffffff;--radius: 12px;--space: clamp(12px, 2vw, 20px);}.btn {padding: calc(var(--space) / 2) var(--space);border-radius: var(--radius);background: var(--brand);color: var(--brand-ink);}/* a variant only swaps the token */.btn--danger { --brand: #d93025; }/* fallback value when the variable is missing */.card { background: var(--card-bg, var(--surface)); }/* JavaScript can change it live *//* document.documentElement.style.setProperty("--brand", "#0db7ed"); */
Why it matters A variant that only changes a token keeps every other rule shared, so buttons cannot drift apart over time.
Responsive design adapts one page to every screen with fluid layouts, flexible media and media queries that change rules at breakpoints.
In detail
Write mobile first: base styles for small screens, then min-width queries add complexity. Choose breakpoints where your content breaks, not at device widths. Media queries also read user preferences: prefers-color-scheme, prefers-reduced-motion and hover capability.
Flexbox lays items out along one axis, a row or a column, and controls how they grow, shrink, wrap and align.
In detail
Set display: flex on the parent. justify-content aligns along the main axis, align-items across it, and gap spaces items. On children, flex: 1 shares leftover space and flex-wrap lets items move to the next line. Flexbox is ideal for navbars, toolbars, button groups and centering.
Grid lays items out in two dimensions with rows and columns you define, then places items by line numbers or named areas.
In detail
grid-template-columns: repeat(3, 1fr) makes three equal columns. grid-template-areas draws a layout with words. Items can span with grid-column: span 2. subgrid lets nested items align to the parent grid. Use grid for page layouts and card galleries, flexbox for one dimensional rows.
Container queries style a component based on the width of its container, not the screen, so the same card can be compact in a sidebar and wide in the main column.
In detail
Mark a parent with container-type: inline-size and an optional container-name, then write @container rules. Container query units such as cqi size things relative to the container. This is the missing piece that makes truly reusable components possible.
Modern CSS supports nesting without a preprocessor, :has() as a parent selector, logical properties for any writing direction, and accent-color, aspect-ratio and scroll snapping out of the box.
In detail
Nesting with & keeps related rules together. :has() selects an element based on what it contains, such as a card that has an image. margin-inline and padding-block adapt to right to left languages. Check caniuse before relying on the newest features and provide a sensible fallback.
Transitions animate a property from one value to another when it changes, and transforms move, scale and rotate elements without affecting layout.
In detail
Animate transform and opacity whenever possible because the browser can run them on the compositor without recalculating layout. Use individual properties translate, scale and rotate for readability, and pick easing such as ease-out for entrances.
@keyframes define the steps of an animation, and the animation property plays them with a duration, delay, easing, direction and repeat count.
In detail
Use keyframes for loaders, attention pulses, floating illustrations and entrances. Negative delays start an animation partway through, which staggers many items without JavaScript. Scroll driven animations with animation-timeline: view() now link progress to scroll in supporting browsers.
Theming swaps a small set of custom properties. prefers-color-scheme follows the system, and a data-theme attribute lets people choose for themselves.
In detail
Define light tokens on :root, dark tokens under the media query and under [data-theme="dark"], and make every component use tokens only. Set color-scheme so form controls and scrollbars match. Dark mode is not inverted colours: lower contrast slightly, lift surfaces with lighter greys, and desaturate brand colours.
Why it matters Writing the dark tokens twice, once for the system preference and once for the explicit choice, lets a manual toggle override the system either way.
As a site grows, naming and structure matter more than any single property. BEM, utility classes and cascade layers are three ways to keep CSS predictable.
In detail
BEM names blocks, elements and modifiers as card, card__title and card--featured, which keeps specificity flat and intent obvious. Utility first CSS such as Tailwind composes small single purpose classes. Layers order resets, base, components and utilities so the cascade stays under control. Most teams mix these approaches.
JavaScript can sit in an event attribute, inside a script element, or in an external file loaded with src. How you load external files, with defer, async or type="module", decides when they run.
In detail
Inline handlers such as onclick mix markup and behaviour and are blocked by strict security policies. A plain script in the head blocks parsing. defer downloads in parallel and runs after the HTML is parsed, in order. async runs as soon as it downloads, in any order, which suits independent scripts like analytics. Modules are deferred by default.
<!-- Inline handler: quick, but mixes HTML with behaviour --><buttononclick="alert('Hi')">Say hi</button><!-- Blocked by a Content Security Policy without 'unsafe-inline' --><!-- Prefer addEventListener in a script file instead -->
Why it matters Inline handlers cannot be reused, are hard to test, and are the first thing a strict CSP blocks.
<head><!-- runs after parsing, in document order --><scriptsrc="vendor.js" defer></script><scriptsrc="app.js" defer></script><!-- runs as soon as it arrives, order not guaranteed: good for analytics --><scriptsrc="analytics.js" async></script><!-- ES modules: deferred by default, can import other files --><scripttype="module"src="main.js"></script><script nomodule src="legacy.js"></script></head>
Why it matters defer is the safe default for your own scripts. It never blocks parsing and keeps execution order.
Variables store values. Use const by default, let when the value changes, and never var. JavaScript has primitive types such as string, number, boolean, null and undefined, and objects for everything else.
In detail
const prevents reassignment, not mutation: a const array can still get new items. typeof reports the type, with the famous quirk that typeof null is object. Template literals with backticks embed values in strings. Numbers are floating point, so 0.1 + 0.2 is not exactly 0.3; use BigInt or integers of cents for money.
types.jsJavaScript
const name = "Ada";let count = 0;const greeting = `Hello, ${name}. You have ${count} messages.`;
const name = "Ada"; // stringlet count = 0; // number, will changeconst isAdmin = false; // booleanlet selected = null; // intentionally emptylet notSetYet; // undefinedconst tags = ["html", "css"]; // object (array)const user = { name, isAdmin };count += 1;tags.push("js"); // allowed: const stops reassignment, not mutationconsole.log(typeof name); // "string"console.log(typeofnull); // "object" (a historic quirk)console.log(Array.isArray(tags)); // trueconst greeting = `Hello, ${name}. You have ${count} message${count === 1 ? "" : "s"}.`;console.log(greeting);console.log(0.1 + 0.2); // 0.30000000000000004const priceInPaise = 1999; // store money as integers
Why it matters Template literals read naturally and avoid the + chains that made old string building error prone.
Comparison, logical and arithmetic operators combine values, and if, else, switch and the ternary operator choose what runs.
In detail
Always compare with === and !==, which do not convert types. && and || short circuit, ?? supplies a default only for null or undefined, and ?. reads deep properties safely. Truthy and falsy values let you write if (list.length) but can surprise you with 0 and empty strings.
Functions package steps you can reuse. They take parameters, return values, and remember the variables around where they were created, which is called a closure.
In detail
Arrow functions are short and do not have their own this. Default parameters and rest parameters keep signatures clean. Variables declared with let and const live in the nearest block. Closures power counters, private state, event handlers and debounce helpers.
functions.jsJavaScript
const add = (a, b = 0) => a + b;functioncounter() { let n = 0; return () => ++n; }
// declaration: hoisted, usable before this linefunctiongreet(name = "friend") {return`Hello, ${name}`;}// arrow function: short, no own `this`const add = (a, b = 0) => a + b;// rest parameters collect the remaining argumentsconst sum = (...numbers) => numbers.reduce((total, n) => total + n, 0);// closure: the inner function remembers `count`functionmakeCounter() {let count = 0;return () => ++count;}const next = makeCounter();next(); // 1next(); // 2// debounce: wait until typing stopsfunctiondebounce(fn, ms = 300) {let timer;return (...args) => {clearTimeout(timer); timer = setTimeout(() => fn(...args), ms); };}const onSearch = debounce((q) => console.log("search", q), 250);
Why it matters debounce is a closure in disguise: timer survives between calls because the returned function remembers it.
Arrays hold ordered lists and objects hold named properties. map, filter, find and reduce transform arrays, and destructuring and spread copy and combine them.
In detail
Prefer methods that return new arrays over mutating loops; it makes code easier to follow and fits how frameworks detect changes. JSON is text that looks like objects; JSON.parse reads it and JSON.stringify writes it. structuredClone makes a deep copy.
for, for...of, while and array methods repeat work. for...of iterates values of arrays, strings, Maps and Sets; for...in iterates object keys and is rarely what you want.
In detail
Use forEach or for...of to perform side effects, map to transform, and a classic for loop when you need the index and early exit with break. Object.entries turns an object into pairs you can loop over. Map and Set are better than objects when keys are not strings or must be unique.
loops.jsJavaScript
for (const item of cart) total += item.price;for (const [key, value] ofObject.entries(settings)) console.log(key, value);
const cart = [{ name: "Mug", price: 299 }, { name: "Tee", price: 799 }];let total = 0;for (const item of cart) { total += item.price;}for (let i = 0; i < cart.length; i++) {if (cart[i].price > 500) { console.log("First expensive item at index", i);break; }}const settings = { theme: "dark", fontSize: 16 };for (const [key, value] ofObject.entries(settings)) { console.log(`${key} = ${value}`);}// Map keeps insertion order and accepts any key typeconst visits = newMap();visits.set("/home", 3).set("/blog", 7);for (const [path, count] of visits) console.log(path, count);// Set removes duplicatesconst unique = newSet(["css", "html", "css"]);console.log(unique.size); // 2
Why it matters Object.entries plus destructuring in for...of is the cleanest way to walk an object's keys and values together.
Classes are blueprints for objects with shared methods. this refers to the object a method was called on, which changes depending on how the function is called.
In detail
A constructor sets up fields, methods live on the prototype, # fields are truly private, and extends with super builds on another class. In event handlers this is the element unless you use an arrow function or bind. Many modern codebases prefer plain functions and objects, but classes remain common in web components and libraries.
Modules split code across files. export shares values, import brings them in, and the browser loads them with script type="module".
In detail
Named exports suit utilities; a default export suits a file with one main thing. Module scripts run in strict mode, are deferred, and load relative paths with the file extension. Dynamic import() loads code only when needed, which is how bundlers and frameworks split large apps.
JavaScript runs on one thread, so slow work such as network requests is asynchronous. A promise represents a future value, and async and await let you write that waiting code top to bottom.
In detail
The event loop runs your code, then queued callbacks. A promise is pending, then fulfilled or rejected. await pauses only the current async function, not the page. Promise.all runs tasks in parallel and fails fast, Promise.allSettled waits for all, and AbortController cancels requests.
try, catch and finally handle errors, throw raises them, and the console, breakpoints and the debugger statement help you find their cause.
In detail
Catch errors where you can do something useful, such as showing a message or retrying. Throw Error objects, not strings, so you get a stack trace. Add a global handler for unhandled promise rejections to report crashes. In DevTools, set breakpoints, step through code and inspect variables instead of scattering console.log everywhere.
classValidationErrorextendsError {constructor(field, message) {super(message);this.name = "ValidationError";this.field = field; }}functionparseAge(value) {const age = Number(value);if (!Number.isInteger(age) || age < 0) thrownewValidationError("age", "Age must be a whole number");return age;}asyncfunctionsubmit(form) {const spinner = form.querySelector(".spinner"); spinner.hidden = false;try {const age = parseAge(form.age.value);awaitsaveProfile({ age }); } catch (err) {if (err instanceofValidationError) showFieldError(err.field, err.message);elseshowToast("Something went wrong. Please try again."); } finally { spinner.hidden = true; // runs whether it worked or not }}// last line of defence for forgotten awaitswindow.addEventListener("unhandledrejection", (event) => { console.error("Unhandled:", event.reason);});
Why it matters finally guarantees cleanup such as hiding a spinner even when the request throws.
06
Phase 06, modules 49 to 56
Talking to the page
DOM, events and browser APIs
Selecting and changing elements, reacting to people, fetching data and remembering things.
The DOM is the live tree of objects the browser builds from HTML. querySelector and querySelectorAll find elements with the same selectors you use in CSS.
In detail
document is the root. Every element has parent, children and siblings you can walk. querySelector returns the first match or null; querySelectorAll returns a static list you can loop with forEach. closest() walks up to the nearest matching ancestor, which is the key to event delegation.
document
htmldocument.documentElement
headtitle, meta, link
bodydocument.body
header
navquerySelector('nav')
main
article.cardquerySelectorAll('.card')
h2card.querySelector('h2')
p#text node inside
a.card__linkel.closest('.card') walks up
footer
Every element is a node with a parent, children and siblings you can walk from JavaScript.
JavaScript changes text, attributes, classes and styles, and creates, moves and removes elements. classList and dataset are the everyday tools.
In detail
Use textContent to set text safely; innerHTML parses HTML and is a security risk with user input. createElement plus append builds nodes, template elements clone ready made markup, and a DocumentFragment batches many insertions into one update.
Events fire when people click, type, scroll or submit. addEventListener attaches a handler, and the event object says what happened and where.
In detail
Events bubble up from the target to its ancestors, so one listener on a parent can handle clicks on many children. That is event delegation, and it works for elements added later. preventDefault stops default actions such as following a link; passive listeners tell the browser a scroll handler will not block scrolling.
JavaScript can intercept submit, read every field with FormData, validate, show inline errors and send data with fetch without reloading the page.
In detail
Listen for submit on the form rather than click on the button so Enter works. new FormData(form) collects named fields, and Object.fromEntries turns it into an object. setCustomValidity plugs your own rules into native validation messages.
fetch sends HTTP requests from JavaScript and returns a promise of a response. You check the status, read JSON, and render it.
In detail
fetch only rejects on network failure, so always check response.ok for 4xx and 5xx. Send JSON with a Content-Type header and JSON.stringify. Use AbortController to cancel stale requests, for example when someone types a new search before the old one returns. Cross origin requests follow CORS rules set by the server.
search.jsJavaScript
const res = awaitfetch(`/api/search?q=${encodeURIComponent(q)}`);if (!res.ok) thrownewError(res.status);const results = await res.json();
localStorage, sessionStorage, cookies and IndexedDB
Browsers can remember data: localStorage persists strings, sessionStorage lasts for a tab, cookies travel with every request, and IndexedDB stores large structured data.
In detail
Use localStorage for small preferences such as a theme, wrapped in try and catch because it can be full or blocked. Never store tokens or secrets there, since any script on the page can read it. Session cookies set by the server with HttpOnly and Secure are safer for authentication.
IntersectionObserver, ResizeObserver and MutationObserver
Observers tell you when something changes without checking in a loop: when an element enters the viewport, when it resizes, or when the DOM changes.
In detail
IntersectionObserver powers lazy loading, infinite scroll, scroll reveal animations and the active link in a table of contents. ResizeObserver reacts to an element's size, which media queries cannot see. MutationObserver watches the DOM for added or changed nodes. All of them are cheaper than scroll and resize handlers.
setTimeout runs code once after a delay, setInterval repeats it, and requestAnimationFrame runs code right before the next screen paint for smooth animation.
In detail
Always keep the id so you can clear timers. Use requestAnimationFrame for anything visual: it matches the display refresh rate and pauses in background tabs. Read layout values such as getBoundingClientRect before writing styles in the same frame to avoid forced reflow.
A card groups an image or video, a title, short text and an action into one clickable unit. Media items are cards laid out horizontally, with the media beside the text.
In detail
Build cards from semantic parts: article for the card, img with aspect-ratio and object-fit for the media, a heading, and one link stretched over the card with a ::after overlay so the whole card is clickable without nesting links. Put cards in an auto fitting grid and let container queries switch to the media item layout when there is room.
A navbar holds the logo and main links. On small screens the links collapse behind a hamburger button that toggles a panel.
In detail
Use a real button with aria-expanded and aria-controls, and update both when it toggles. Keep the menu in the HTML so it works and is indexable even before JavaScript loads. Close it on Escape and when a link is chosen, and trap nothing: let Tab move naturally.
The hero is the first screen: a headline, a supporting sentence, one primary action, and usually an illustration or image.
In detail
Lead with what the site does in plain words. Give the main button the strongest colour and a secondary link a quieter style. Use a two column grid that stacks on phones, clamp() for the headline size, and preload the hero image so it is not your slowest paint.
Live previewrendered from the code below
Build the web, one block at a time
From raw HTML to a complete page.
Start readingModules
hero.htmlHTML
<sectionclass="hero"><h1>Ship websites faster</h1><p>Everything you need, nothing you do not.</p><aclass="btn"href="#start">Get started</a></section>
<sectionclass="hero"><divclass="hero__copy"><pclass="hero__kicker">New guide</p><h1class="hero__title">Build the web, one block at a time</h1><pclass="hero__lede">From raw HTML to a complete, responsive, animated page.</p><divclass="hero__ctas"><aclass="btn btn--primary"href="#start">Start reading</a><aclass="btn"href="#modules">See all modules</a></div></div><imgclass="hero__art"src="hero.svg"alt=""width="560"height="460"fetchpriority="high"/></section><style> .hero { display: grid; grid-template-columns: 1.1fr 0.9fr; align-items: center; gap: 32px; min-height: min(90dvh, 760px); padding: 48px 24px; } .hero__title { font-size: clamp(2.2rem, 1.2rem + 4vw, 4.2rem); line-height: 1.02; letter-spacing: -0.04em; margin: 8px 00; } .hero__lede { font-size: 1.15rem; max-width: 52ch; color: #4a5872; } .hero__ctas { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; } @media (max-width: 800px) { .hero { grid-template-columns: 1fr; } }</style>
Why it matters fetchpriority="high" tells the browser to fetch the hero image before other images, which usually improves Largest Contentful Paint.
Buttons perform actions, links go places, and chips are small pill shaped labels or filters. Getting the right element matters more than the styling.
In detail
Use button for actions and a for navigation, even if they look the same. Give interactive elements a minimum target of about 44 pixels, visible hover, active and focus states, and a disabled style. Chips work well as hashtag style links to topic pages, which also helps search engines connect related content.
The native dialog element opens as a modal with showModal(), traps focus, closes on Escape, and dims the page with ::backdrop, all without a library.
In detail
Give the dialog a heading and a close button, return focus to the opener when it closes, and use method="dialog" on a form to close it with a value. Keep modals for decisions that block the task; use toasts and inline messages for everything else.
Accordions stack collapsible sections and tabs switch between panels. details and summary give you an accessible accordion with no JavaScript at all.
In detail
Add a shared name attribute to details elements to make them exclusive, so opening one closes the others. Tabs need role="tablist", role="tab" with aria-selected and aria-controls, and arrow key navigation; keep content in the HTML so it is indexable.
A horizontal list with overflow and scroll-snap makes a swipeable carousel that works with touch, trackpads and keyboards, using only CSS.
In detail
Each slide snaps into place with scroll-snap-align. Add previous and next buttons that call scrollBy for mouse users, and keep autoplay off or pausable. Native scrolling is smoother and more accessible than most JavaScript sliders.
A toast is a short message that appears briefly in a corner to confirm an action, such as Saved or Copied, then disappears.
In detail
Put toasts in a container with role="status" or aria-live="polite" so screen readers announce them. Do not hide errors that need action in a toast, and pause the timer on hover. The popover attribute is a modern way to layer such UI above everything without z-index battles.
SVG draws icons and illustrations with vectors that stay sharp at any size and can be coloured with CSS. Sprite sheets and icon sets like Iconify make large icon libraries easy.
In detail
Inline SVG with fill="currentColor" inherits text colour, so icons follow themes automatically. A sprite with symbol and use avoids repeating paths. Decorative icons get aria-hidden="true"; meaningful icons need a label. Icon fonts are outdated: they blur, fail to load and confuse screen readers.
Images are usually the heaviest part of a page. Right sizing, modern formats, lazy loading and reserved space make them fast and stable.
In detail
Export at the largest size actually displayed, compress, and prefer AVIF or WebP. Set width and height or aspect-ratio to avoid layout shift. Lazy load below the fold, but never the hero. Use a CDN that resizes on the fly when you have many images.
A single page website stacks sections on one page: header and navigation, hero, features, cards, testimonials, FAQ and footer, with in page links between them.
In detail
Start with semantic HTML for every section, then a design token layer, layout with grid and flexbox, and components. Add JavaScript last for the hamburger, theme toggle and reveals, so the page still works if a script fails. Give each section an id for deep links and scroll-margin-top so headings are not hidden under a sticky header.
Sections fade and slide in as they enter the viewport. IntersectionObserver adds a class once, and CSS does the animation.
In detail
Start content visible and only hide it when JavaScript is running, so nothing stays invisible if a script fails. Animate opacity and transform only, reveal each element once, and stagger children with a CSS variable for delay. Respect prefers-reduced-motion by skipping the effect.
Lenis smooths wheel and trackpad scrolling while keeping native scroll behaviour. Parallax moves layers at different speeds as you scroll to suggest depth.
In detail
Create one Lenis instance, drive it from requestAnimationFrame, and use lenis.scrollTo for in page links. For parallax, read each layer's position on scroll and translate it by a fraction of its distance from the centre of the screen. Turn both off for prefers-reduced-motion, and mark inner scroll areas with data-lenis-prevent.
A theme toggle sets data-theme on the html element, saves the choice, and restores it before the page paints so there is no flash of the wrong theme.
In detail
Read the saved choice in a tiny inline script in the head, before the stylesheet applies, then fall back to the system preference. Update aria-pressed on the button and the theme-color meta tag so the mobile browser bar matches.
Single key shortcuts such as T for theme, F for full screen, M for menu and slash for search make a long page fast to use, as long as they never fire while typing.
In detail
Listen for keydown on document, ignore events with modifier keys and events from inputs, textareas and contenteditable. Document the shortcuts on the page. The Fullscreen API toggles full screen with requestFullscreen and exitFullscreen, and fires fullscreenchange so your button can stay in sync.
Sass adds variables, nesting, mixins, functions and partials to CSS, then compiles to plain CSS. It was the first big answer to large stylesheets.
In detail
Many Sass features now exist natively: custom properties, nesting and color-mix. Sass still shines for loops that generate utility classes, mixins for repeated patterns, and splitting styles into partials joined with @use. Vite and most frameworks compile .scss files automatically once the sass package is installed.
Tailwind gives you small single purpose classes such as flex, p-4 and text-blue-600 that you combine in HTML. A build step keeps only the classes you use.
In detail
Utility first trades long class lists for speed and consistency: spacing, colours and breakpoints come from one theme, and there is no naming or dead CSS. Prefixes handle states and breakpoints: hover:, md:, dark:. Extract repeated patterns into components in your framework rather than into custom CSS classes.
Bootstrap ships a grid, ready styled components such as navbars, cards and modals, and a JavaScript bundle to make them work. It is the fastest way to a consistent, conventional site.
In detail
Use the 12 column grid with container, row and col classes, add components by copying their markup, and customise colours through Sass variables or CSS variables. The trade off is that Bootstrap sites look alike unless you theme them, and you ship styles for components you may not use.
CSS in JS writes styles in JavaScript files next to components, generates unique class names, and can change styles from props. JSS, styled-components and Emotion are the best known.
In detail
It solved global naming clashes and made dynamic styles easy in React. The cost is runtime work in the browser and friction with server components. Newer zero runtime tools such as vanilla-extract and Panda extract static CSS at build time, and many teams now prefer CSS Modules or Tailwind instead.
CSS Modules are ordinary CSS files whose class names are made unique at build time, so .title in one component never clashes with .title in another.
In detail
Name the file Card.module.css, import it as an object, and use styles.title as the class. You keep plain CSS, zero runtime cost and full support for media queries and variables. Vite and Next.js support CSS Modules without configuration.
Component libraries such as MUI, Chakra, Radix and shadcn/ui ship accessible, themeable components for frameworks. A design system is your own documented set of tokens and components.
In detail
Headless libraries like Radix give behaviour and accessibility with no styles, so you bring Tailwind or CSS. Styled libraries like MUI give a complete look. shadcn/ui copies component source into your project so you own it. Whatever you pick, define tokens first so components stay consistent.
Ways to style a site, compared
Approach
You write
Ships
Strength
Watch out for
Plain CSS
Selectors and properties
Only your CSS
No build, full control
Naming collisions at scale
Sass
SCSS with nesting and mixins
Compiled CSS
Organisation, reuse
Needs a build step
Tailwind
Utility classes in HTML
Only used utilities
Speed, consistency
Long class lists
Bootstrap
Component classes
Framework CSS and JS
Fast prototypes
Sites look alike
CSS Modules
Scoped .module.css files
Hashed class names
No collisions
Framework tooling needed
CSS in JS
Styles inside components
Runtime or extracted CSS
Dynamic styles
Runtime cost
Component libraries
Imported components
Pre built UI
Accessible behaviour
Customising deeply
10
Phase 10, modules 78 to 85
Why frameworks
React and Next.js
The problem plain DOM code runs into, how React solves it, and why Next.js wraps React for real websites.
Plain JavaScript works until the interface has lots of state: you end up writing code that keeps the DOM in sync by hand. Frameworks let you describe what the UI should look like for a given state and update the DOM for you.
In detail
jQuery made DOM work easier but still manual. React, released by Facebook in 2013, introduced components and a declarative model: UI is a function of state. Single page apps rendered in the browser were slow to first paint and hard for search engines, so Next.js added server rendering, static generation, routing and image optimisation on top of React.
1991HTMLTim Berners-Lee publishes the first web pages.
1995JavaScriptBrendan Eich writes it in ten days at Netscape.
1996CSSStyling moves out of the markup.
2006jQueryWrite less, do more: one API over messy browsers.
2010AngularJSTwo way binding and single page apps go mainstream.
2013ReactComponents and UI as a function of state.
2014VueA gentler, progressive framework.
2016Next.jsReact with routing and server rendering built in.
2017TailwindUtility first CSS takes off.
2023Server componentsRendering moves back toward the server.
vanilla vs reactJavaScript
// vanilla: you update the DOM yourselfcount++; label.textContent = count; button.disabled = count >= 10;// react: you describe the result<button disabled={count >= 10}>{count}</button>
// ---------- Vanilla: imperative ----------let count = 0;const button = document.querySelector("#like");const label = document.querySelector("#like-count");button.addEventListener("click", () => { count++; label.textContent = count; // remember every place that shows count button.disabled = count >= 10; // and every rule that depends on it button.classList.toggle("hot", count > 5);});// ---------- React: declarative ----------import { useState } from"react";exportfunctionLikeButton() {const [count, setCount] = useState(0);return ( <button disabled={count >= 10} className={count > 5 ? "hot" : ""} onClick={() => setCount(count + 1)}>Like <span>{count}</span> </button> );}
Why it matters In React the UI is recalculated from state every time, so a new rule is one expression instead of another manual DOM update.
A React component is a function that returns JSX, HTML like syntax inside JavaScript. Props pass data into components, and components compose into bigger ones.
In detail
JSX uses className instead of class and htmlFor instead of for, and wraps expressions in braces. Components start with a capital letter. Keep components small and pure: same props, same output. children lets a component wrap other markup, which is how layouts and cards are built.
Card.jsxJavaScript
exportfunctionCard({ title, minutes, children }) {return <article className="card"><h3>{title}</h3><p>{minutes} min</p>{children}</article>;}
useState gives a component memory that triggers a re-render when it changes. useEffect runs side effects such as fetching data or subscribing to events after render.
In detail
Never mutate state; set a new value. Derive values during render instead of storing duplicates. Effects need a dependency array and a cleanup function for subscriptions and timers. Data fetching in modern apps usually moves to the framework, such as Next.js server components or a library like TanStack Query.
Render lists with map and give each item a stable key. Controlled inputs keep form values in state so React is the single source of truth.
In detail
Keys must be unique among siblings and stable, such as an id; using the array index breaks when items are reordered. Filter and sort during render. React 19 form actions let a form call a function directly with FormData, which simplifies submissions.
Next.js is a React framework that adds file based routing, server rendering, static generation, image and font optimisation, and API routes, so a React app becomes a fast, indexable website.
In detail
Plain React renders in the browser, so the first HTML is empty until JavaScript loads, which hurts speed and SEO. Next.js renders on the server or at build time and sends real HTML, then React takes over for interactivity. Server Components keep data fetching and heavy code on the server and send less JavaScript.
terminalShell
npx create-next-app@latest my-blogcd my-blog && npm run dev
# scaffold with TypeScript, ESLint, Tailwind and the App Routernpx create-next-app@latest my-blog --ts --eslint --tailwind --appcd my-blognpm run dev # http://localhost:3000 with hot reloadnpm run build # production build: prerenders static pagesnpm start # run the production server
Why it matters The build output lists which routes are static and which are dynamic, which is the quickest way to understand how your site is served.
In the App Router, folders in app define routes, page.jsx renders a page, layout.jsx wraps pages with shared UI, and an exported metadata object writes the SEO tags.
In detail
Components are server components by default and can be async to fetch data directly. Add "use client" at the top of a file for state, effects and event handlers. Dynamic segments like [slug] read params, generateStaticParams prerenders them, and next/image and next/link optimise images and navigation.
app/blog/[slug]/page.jsxJavaScript
exportdefaultasyncfunctionPost({ params }) {const post = awaitgetPost((await params).slug);return <article><h1>{post.title}</h1></article>;}
Rendering strategy decides where HTML is built: in the browser (CSR), on the server per request (SSR), once at build time (SSG), or at build time with periodic refresh (ISR).
In detail
Static is fastest and cheapest, perfect for blogs and docs. Server rendering suits personalised or fast changing pages. Client rendering suits dashboards behind a login. Modern frameworks mix them per route, and streaming sends parts of a page as soon as they are ready.
React is not the only option. Vue offers approachable templates, Svelte compiles components away, Angular is a full platform, and Astro ships zero JavaScript by default with islands of interactivity.
In detail
Each has a meta framework: Nuxt for Vue, SvelteKit for Svelte, Analog for Angular. Astro suits content sites and blogs because pages are static HTML unless a component opts into JavaScript. The core ideas, components, state, props and routing, transfer between all of them.
npm installs open source packages and records them in package.json. Scripts in package.json run common tasks such as dev, build and test.
In detail
dependencies ship with the app, devDependencies are only for building. The lockfile pins exact versions so every machine installs the same thing; commit it. Semantic versions read major.minor.patch, and a caret allows minor and patch updates. Check a package's size and maintenance before adding it.
A bundler takes your modules, CSS and assets and produces optimised files for production. Vite serves files instantly in development and bundles with Rollup for production.
In detail
Vite gives a dev server with hot module replacement, supports TypeScript, JSX, CSS Modules, Sass and PostCSS out of the box, and hashes file names so browsers can cache them forever. Webpack, Parcel, esbuild and Turbopack solve the same problem with different trade offs.
terminalShell
npm create vite@latest my-site -- --template vanillacd my-site && npm install && npm run dev
# vanilla JS, React, Vue, Svelte and more templatesnpm create vite@latest my-site -- --template vanillacd my-sitenpm installnpm run dev # instant dev server with hot reloadnpm run build # outputs dist/ with hashed, minified filesnpm run preview # serve the production build locally# dist/# index.html# assets/index-4f9a2c1b.js# assets/index-91d0e7aa.css
Why it matters Hashed file names change whenever content changes, so you can cache assets for a year without serving stale code.
TypeScript adds types to JavaScript so editors catch mistakes as you type: a missing property, a wrong argument, a possibly null element.
In detail
Types are erased at build time, so the browser still runs JavaScript. Start with type inference, add types to function parameters and shared data, and turn on strict mode early. querySelector returns Element | null, which forces you to handle a missing element instead of crashing.
cart.tsTypeScript
typeItem = { id: string; price: number };const total = (items: Item[]): number => items.reduce((s, i) => s + i.price, 0);
typeItem = { id: string; name: string; price: number; // in paise tags?: string[]; // optional};functiontotal(items: Item[]): number {return items.reduce((sum, item) => sum + item.price, 0);}const badge = document.querySelector<HTMLSpanElement>("#cart-count");if (badge) badge.textContent = String(3); // TypeScript forces the null checktypeTheme = "light" | "dark";functionapplyTheme(theme: Theme) { document.documentElement.dataset.theme = theme;}applyTheme("dark");// applyTheme("blue"); // error: not assignable to type Theme
Why it matters A union of string literals such as "light" | "dark" turns typos into compile errors.
Prettier formats code the same way for everyone, and ESLint flags likely bugs such as unused variables or missing awaits. Together they end style debates and catch mistakes early.
In detail
Run Prettier on save in your editor, ESLint in the editor and in CI, and a pre commit hook so bad code never lands. Stylelint does the same for CSS, and axe or eslint-plugin-jsx-a11y catch accessibility mistakes.
Git records every change to your project so you can go back, branch off and merge. GitHub hosts repositories, reviews pull requests and runs automation.
In detail
Commit small, focused changes with clear messages. Work on a branch, open a pull request, and merge after review. A .gitignore keeps node_modules, builds and secrets out. GitHub Actions can lint, test, build and deploy on every push.
Core Web Vitals measure real user experience: Largest Contentful Paint for loading, Interaction to Next Paint for responsiveness, and Cumulative Layout Shift for visual stability.
In detail
Aim for LCP under 2.5 seconds, INP under 200 milliseconds and CLS under 0.1. Ship less JavaScript, compress and cache assets, preload the hero image and fonts, reserve space for media, and break long tasks. Measure with Lighthouse in the lab and with real user data in the field.
The most common front end risks are cross site scripting (XSS), unsafe third party scripts, leaking secrets, and mixed content. HTTPS, careful DOM handling and a Content Security Policy prevent most of them.
In detail
Never put untrusted input into innerHTML; use textContent or a sanitiser. Keep API keys on the server, because anything in front end code is public. A CSP header limits where scripts can load from, Subresource Integrity checks CDN files, and cookies for sessions should be HttpOnly, Secure and SameSite.
const comment = newURLSearchParams(location.search).get("comment") ?? "";// unsafe: a comment like <img src=x onerror=alert(1)> would run// output.innerHTML = comment;// safe: rendered as text, never parsed as HTMLconst output = document.querySelector("#comment");output.textContent = comment;// if you must render HTML from users, sanitise it first// import DOMPurify from "dompurify";// output.innerHTML = DOMPurify.sanitize(comment);/* response header from the serverContent-Security-Policy: default-src 'self'; script-src 'self' https://cdn.jsdelivr.net; img-src 'self' data:; object-src 'none'; base-uri 'self'<script src="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.min.js" integrity="sha384-..." crossorigin="anonymous"></script>*/
Why it matters textContent can never execute anything, which makes it the default choice for showing data you did not write.
Docker packages your site and its web server into an image that runs the same on any machine. A multi stage build compiles with Node, then serves the static files with a tiny Nginx image.
In detail
The first stage installs dependencies and runs the build; the second copies only dist into nginx:alpine, so the final image is a few megabytes with no Node or source code. Add a .dockerignore, an Nginx config for caching and single page app fallbacks, and run it locally with docker compose before deploying.
3 examples
DockerfileDockerfile
FROM node:22-alpine AS buildRUN npm ci && npm run buildFROM nginx:alpineCOPY --from=build /app/dist /usr/share/nginx/html
Static sites can be hosted on GitHub Pages, Netlify, Vercel or Cloudflare Pages for free; containers run on platforms that accept Docker images. A CDN copies files to servers near your visitors.
In detail
Connect a repository and the host builds and deploys on every push, with preview links for pull requests. Point your domain's DNS at the host and HTTPS certificates are issued automatically. Set cache headers so hashed assets are cached for a year and HTML is revalidated.
A progressive web app adds a manifest and a service worker so a site can be installed, open in its own window, and work offline.
In detail
The manifest names the app, sets icons and theme colours, and chooses a display mode. The service worker is a script that sits between the page and the network, caching files so repeat visits are instant and offline visits still work. Start with caching the app shell and a fallback page.
sw.jsJavaScript
self.addEventListener("fetch", (e) => e.respondWith(caches.match(e.request).then((r) => r ?? fetch(e.request))));
Browsers expose rich APIs: Canvas for drawing, Clipboard, Web Share, Geolocation, Notifications, Web Audio, View Transitions and many more.
In detail
Feature detect before using any API and ask for permissions only when the person triggers the feature. The View Transitions API animates between page states with one function call. Web Components let you define your own reusable HTML elements without a framework.
Build a single page blog that uses every phase: semantic HTML, a token based theme with dark mode, responsive grid and cards, a hamburger navbar, scroll reveals, Lenis smooth scrolling, keyboard shortcuts, then rebuild one section in React and ship the whole thing in Docker.
In detail
Scope: a hero with an illustration, a module index in a hamburger menu, article cards with hashtag chips, a dictionary, an FAQ accordion, a documentation board and a footer. Keep Lighthouse scores above 90 for performance, accessibility and SEO, test with a keyboard and a screen reader, and deploy from GitHub with a pipeline. This page you are reading is one possible answer.
Why did React come up if JavaScript can already change the page?
Keeping many parts of the screen in sync with data by hand gets error prone. React lets you describe the UI for each state and updates the DOM for you.
No. A dashboard behind a login can be plain React with Vite. Next.js pays off when you need routing, SEO friendly server rendering or static generation.
Store colours as custom properties, redefine them under prefers-color-scheme: dark and a data-theme attribute, and set color-scheme so form controls follow.
Every source linked from the modules above, grouped by the phase that uses it and then by where it lives. 290 links in total, all opening in a new tab.