Effects.lab
Complete Guide
Web
Basics
The guide that teaches you to build solid websites, from your first HTML tag to secure deployment
HTML · CSS · JavaScript · Responsive · Accessibility · SEO · Tools · Security
Table of Contents
01 HTML — Structure
01Give your page a solid structureStructurep.4
02Give meaning to your tagsSemanticsp.4
03Create working formsFormsp.5
04Organize your data in tablesTablesp.5
05Integrate images, videos and audioMultimediap.6
06Link your pages togetherLinksp.6
02 CSS — Styling
07Target exactly what you want to styleSelectorsp.7
08Master the space around each elementBox Modelp.7
09Align your elements in a lineFlexboxp.8
10Create grid-based layoutsGridp.8
11Centralize your colors and valuesVariablesp.9
12Add movement to your elementsTransitionsp.9
13Adapt your design to every screenResponsivep.10
03 JavaScript — Interactivity
14Store and manipulate your dataVariablesp.11
15Select and modify your page in real timeDOMp.11
16React to user actionsEventsp.12
17Exchange data with a serverFetch/APIp.12
18Save data in the browserlocalStoragep.13
19Write modern, readable JavaScriptES6+p.13
04 Responsive — Adaptation
20Configure the mobile viewportViewportp.14
21Design mobile-firstMobile-firstp.14
22Define your breakpointsBreakpointsp.15
23Adapt your images to every screenImagesp.15
24Make your touch buttons comfortableTouchp.16
25Test on real devicesTestingp.16
05 Accessibility — Inclusion
26Make your page readable by everyoneSemantic HTMLp.17
27Enhance accessibility with ARIAARIAp.17
28Choose readable colorsWCAG Contrastp.18
29Make everything keyboard accessibleKeyboardp.18
30Communicate with screen readersScreen readersp.19
31Respect user preferencesPreferencesp.19
06 SEO & Performance — Visibility
32Help Google understand your pageMeta tagsp.20
33Structure your headings logicallyHeadingsp.20
34Get a good performance scoreCore Web Vitalsp.21
35Speed up your image loadingImage optimizationp.21
36Preload the right resourcesPreload/Prefetchp.22
37Reduce your code weightMinificationp.22
07 Tools — Productivity
38Configure your code editorVS Codep.23
39Save every version of your codeGitp.23
40Inspect and debug in the browserDevToolsp.24
41Put your site online for freeDeploymentp.24
42Choose your domain name and hostingDomain/Hostingp.25
08 Security — Protection
43Encrypt exchanges with HTTPSHTTPS/SSLp.26
44Control what your page can loadCSPp.26
45Prevent malicious code injectionXSSp.27
46Protect your forms against fake submissionsCSRFp.27
47Never trust user dataSanitizationp.28
48Add browser-side shieldsSecurity headersp.28
This guide is made for you
🚀
From zero to a live website
Each chapter helps you progress step by step, from raw HTML to production deployment.
💡
Understand, not just copy
An "In plain terms" block explains each concept with everyday analogies.
⚙
Customize it
Each topic tells you which values to change and what it actually does.
Tip
No need to read it all at once. Essential = must know. Important = strongly recommended. Useful = to go further.
01
HTML — Structure
6 fundamentals for building solid, well-organized pages
In plain terms: An HTML page is like a building. First you lay the foundation (<html>), then the roof (<head>) containing invisible info, and finally the rooms (<body>) where visible content lives. Without this structure, the browser doesn't know what to display or how.
<!DOCTYPE html> <!-- tells the browser: "this is HTML5" -->
<html lang="fr"> <!-- page language -->
<head>
<meta charset="UTF-8"> <!-- accents and special characters -->
<title>Ma page</title> <!-- title in the browser tab -->
</head>
<body>
<h1>Welcome</h1> <!-- visible page content -->
</body>
</html>
Customize it
- Change
lang="fr" based on your content language (en, es, etc.)
- Add
<meta name="viewport"> so mobile displays correctly
- The
<title> appears in Google: make it count
In plain terms: Using <div> everywhere is like writing a book without chapters or paragraphs. The text is there, but nobody can navigate it. Semantic tags (<header>, <nav>, <main>, <footer>) give each zone a role. Google and screen readers understand your page without seeing it.
<header> <!-- header: logo, site title -->
<nav> <!-- navigation: menu links -->
</header>
<main> <!-- main content (one per page) -->
<article> <!-- standalone content: article, post -->
<section> <!-- thematic group with a heading -->
<aside> <!-- related content: sidebar, inset -->
</main>
<footer> <!-- footer: legal, links -->
Customize it
- Replace your
<div> tags with the semantic tag that matches the role
- Only one
<main> per page, that's the rule
- Each
<section> must contain a heading (<h2>, <h3>...)
In plain terms: A form is a dialogue between your site and the visitor. Each field (<input>) is a question, and the <label> is the tag that tells what the field is for. Without a label, it's like a paper form with no heading: the visitor doesn't know what to write where. The field type (email, tel) even adapts the keyboard on mobile.
<form action="/submit" method="POST">
<label for="email">Your email</label> <!-- clickable label -->
<input type="email" <!-- email keyboard on mobile -->
id="email" <!-- linked to label by for -->
required <!-- required field -->
placeholder="name@example.com"> <!-- gray helper text -->
<button type="submit">Submit</button>
</form>
Customize it
- Change the
type based on the field: text, email, tel, password, number
- Add
required to mandatory fields
- The
placeholder guides, but never replaces the label
In plain terms: An HTML table is like an Excel spreadsheet. You have rows (<tr>), header cells (<th>) and data cells (<td>). <thead> and <tbody> separate the header from the content, like the frozen first row in a spreadsheet. Use tables for data, never for layout.
<table>
<thead> <!-- table header -->
<tr>
<th>Nom</th> <!-- header cell (bold) -->
<th>Prix</th>
</tr>
</thead>
<tbody> <!-- table body -->
<tr><td>Widget</td><td>9,90 EUR</td></tr>
</tbody>
</table>
Customize it
- Add
border-collapse: collapse in CSS to remove double borders
- Use
<caption> to give the table an accessible title
- Never use
<table> to align elements: that's Flexbox or Grid's job
In plain terms: Each media type has its dedicated tag. <img> for images, <video> for videos, <audio> for audio. It's like plugging the right cable into the right device. The alt attribute on an image is the description for those who can't see it (visually impaired, slow connection). And width/height reserves space to prevent layout shifts during loading.
<!-- Image with description and reserved dimensions -->
<img src="photo.webp" alt="Sunset over the sea"
width="800" height="450" loading="lazy">
<!-- Video with controls and subtitles -->
<video controls width="640">
<source src="demo.mp4" type="video/mp4">
<track src="sous-titres.vtt" kind="subtitles" srclang="fr">
</video>
Customize it
- Use the WebP format for images (lighter than JPEG/PNG)
- Add
loading="lazy" to images below the fold
- Always add a descriptive
alt, never empty except for decorative images
In plain terms: Links (<a>) are the doors between rooms on your site. An internal link leads to another page on your site, an external link leads elsewhere on the web. The href attribute is the destination address. And target="_blank" opens the door in a new tab, so the visitor doesn't leave your page.
<!-- Internal link (same site) -->
<a href="/contact">Contact us</a>
<!-- External link (other site) — secured -->
<a href="https://example.com"
target="_blank" <!-- new tab -->
rel="noopener noreferrer"> <!-- security: prevents access to your page -->
Visit site
</a>
<!-- Anchor (link to a section of the same page) -->
<a href="#tarifs">View pricing</a>
Customize it
- Always add
rel="noopener noreferrer" to target="_blank" links
- Use descriptive link text: "View pricing" rather than "Click here"
- Anchors (
#section) require an id="section" on the target element
02
CSS — Styling
7 fundamentals for styling and laying out your HTML content
In plain terms: A CSS selector is your element's postal address. The more precise you are, the better you target the right mailbox. p targets all paragraphs. .intro targets elements with the class "intro". #hero targets the unique element with that ID. You can combine them for pinpoint accuracy.
p { color: gray; } /* all paragraphs */
.intro { font-size: 1.2rem; } /* elements with class="intro" */
#hero { background: #6366f1; } /* the element with id="hero" */
.card h2 { color: navy; } /* h2s inside a .card */
.btn:hover { opacity: 0.8; } /* on mouse hover */
input:focus { outline: 2px solid blue; } /* when the field is active */
Customize it
- Prefer classes (
.nom) over IDs (#nom) for reusability
- Avoid overly long selectors (
div.main ul li a): simpler is better
- Pseudo-classes
:hover, :focus, :first-child avoid adding unnecessary classes
In plain terms: Every HTML element is like a package. There's the content (the item), the padding (bubble wrap around the item), the border (the box), and the margin (space between packages on the shelf). By default, the browser adds padding and border on top of the size. With box-sizing: border-box, everything is included in the size you define.
/* Essential: includes padding and border in the size */
* { box-sizing: border-box; }
.carte {
width: 300px; /* total width = 300px, period */
padding: 20px; /* inner space (bubble wrap) */
border: 2px solid #ddd; /* border (cardboard) */
margin: 16px; /* outer space (between packages) */
}
Customize it
- Always put
* { box-sizing: border-box; } at the top of your CSS
- Use
padding for inner space, margin for outer space
- Tip:
margin: 0 auto horizontally centers an element with a fixed width
In plain terms: Imagine a shelf. You place items on it and decide: push them left, center them, or space them evenly. Flexbox does the same with your HTML elements. You tell the parent "become a shelf" with display: flex, then choose how to distribute children with justify-content (horizontal) and align-items (vertical).
.menu {
display: flex; /* activates the shelf */
justify-content: space-between; /* equal space between elements */
align-items: center; /* vertically aligns to center */
gap: 16px; /* fixed space between each element */
}
/* Valeurs courantes de justify-content :
flex-start | center | flex-end | space-between | space-around */
Customize it
- Change
justify-content: center to center, space-between to space out
- Add
flex-wrap: wrap so elements wrap to the next line when space runs out
- Use
gap instead of margins on children: cleaner and more predictable
In plain terms: If Flexbox is a shelf (one dimension), Grid is a chessboard (two dimensions). You define columns and rows, then place your elements in the cells. It's like an architect's blueprint: you draw the grid first, then place the furniture. Ideal for complex layouts with header, sidebar, content, and footer.
.page {
display: grid;
/* 3 columns : the 2nd takes remaining space */
grid-template-columns: 250px 1fr 250px;
gap: 20px; /* space between cells */
}
/* 1fr = 1 fraction de l'espace restant */
/* repeat(3, 1fr) = 3 columns egales */
Customize it
- Change the number of columns:
repeat(3, 1fr) for 3 equal columns
- Use
minmax(250px, 1fr) for flexible columns with a minimum size
- For galleries:
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr))
In plain terms: Imagine painting an apartment. If you write the color reference on each wall, changing your mind means repainting everything. But if you label a can "primary color" and everyone dips into it, just change the can. CSS variables (--nom) work the same way: one value defined once, used everywhere.
/* We define the "paint cans" once */
:root {
--color: #6366f1; /* purple — change here, it changes everywhere */
--text: #1a1a2e; /* text color */
--radius: 12px; /* rounded corners */
--spacing: 16px; /* standard spacing */
}
.button { background: var(--color); border-radius: var(--radius); }
Customize it
- Replace hardcoded values with
var(--nom) throughout your CSS
- Create variables for: colors, spacing, border-radius, shadows
- For dark mode, redeclare the variables in
@media (prefers-color-scheme: dark)
In plain terms: Without transition, a style change is instant: the button is blue, then suddenly it's purple. With transition, you add a fade between two states. It's like a dimmer switch instead of an on/off switch. You choose what changes, how long it takes, and the rhythm (slow at start, fast at end, etc.).
.button {
background: #6366f1;
color: white;
/* transition: what | duration | easing */
transition: background 0.3s ease, transform 0.3s ease;
}
.button:hover {
background: #4f46e5; /* darker purple */
transform: translateY(-2px); /* moves slightly up */
}
Customize it
- Change the duration:
0.2s for fast, 0.5s for slow and elegant
- Use
ease-out for natural animations (fast at start, slow at end)
- Only animate
transform and opacity for optimal performance
In plain terms: A responsive site is like modular furniture that adapts to the room size. Media queries (@media) are conditional rules: "if the screen is less than 768px, stack the columns." You write the mobile design first (smallest), then add rules for larger screens. That's the "mobile first" principle.
/* Base = mobile (small screen) */
.grille {
display: grid;
grid-template-columns: 1fr; /* 1 column on mobile */
gap: 16px;
}
/* Tablet: from 768px */
@media (min-width: 768px) {
.grille { grid-template-columns: 1fr 1fr; } /* 2 columns */
}
/* Desktop: from 1024px */
@media (min-width: 1024px) {
.grille { grid-template-columns: repeat(3, 1fr); } /* 3 columns */
}
Customize it
- Common breakpoints:
640px (mobile), 768px (tablet), 1024px (desktop)
- Use
min-width (mobile first) rather than max-width
- Always test in DevTools (F12) by resizing the window
03
JavaScript — Interactivity
6 fundamentals for making your pages dynamic and interactive
In plain terms: A variable is a labeled box. You store a value in it (a name, a number, a list). let creates a modifiable box. const creates a locked box. Types (text, number, boolean) define what the box can hold. Without variables, your program can't remember anything.
const nom = "Alice"; // text (string) — won't change
let age = 25; // number — can change
let estConnecte = true; // boolean (true/false)
const fruits = ["pomme", "poire"]; // array
const user = { nom: "Alice", age: 25 }; // object
age = 26; // let allows you to change the value
console.log(user.nom); // displays "Alice" in the console
Customize it
- Use
const by default, let only if the value needs to change
- Name your variables clearly:
totalPrice rather than x
- Avoid
var: it's the old syntax, less reliable than let/const
In plain terms: Your HTML page is a tree. Each branch is an element. This tree is called the DOM (Document Object Model — the in-memory version of your page). JavaScript can shake this tree: add branches, cut some, change leaves. That's DOM manipulation. You select an element with querySelector, then modify its text, style, or attributes. Everything happens live, without reloading the page.
// Select an element
const titre = document.querySelector('.titre'); // by class
const btn = document.querySelector('#valider'); // by ID
// Modify content and style
titre.textContent = "Nouveau titre"; // changes the text
titre.style.color = "#6366f1"; // changes the color
titre.classList.add("visible"); // adds a CSS class
Customize it
- Prefer
querySelector (CSS selector) over getElementById
- Use
textContent (secure) instead of innerHTML (XSS risk)
- Manipulate classes with
classList.add/remove/toggle instead of inline styles
In plain terms: An event is something that happens: a click, a key press, a scroll. addEventListener tells JavaScript: "when this happens, execute this action." It's like a motion detector: you place it, and it reacts automatically when someone passes by. Without events, your page is a static poster.
const btn = document.querySelector('#submit');
// Listen for button click
btn.addEventListener('click', () => {
alert('Form submitted!'); // action triggered on click
});
// Listen for a keyboard key (on the whole page)
document.addEventListener('keydown', (e) => {
console.log('Key:', e.key); // displays the pressed key
});
Customize it
- Common events:
click, submit, input, keydown, scroll
- Use
addEventListener (clean) instead of onclick in HTML
- Add
e.preventDefault() to prevent the default behavior (e.g., form submission)
In plain terms: fetch is your site's mailman. It goes to get data from a server via an API (a data counter on a server) and brings it back. The response arrives in JSON format: structured text that JavaScript natively understands. That's how your site displays the weather, loads articles, or sends a form without reloading the page.
// Retrieve data from an API
const response = await fetch('https://api.exemple.fr/users');
const users = await response.json(); // converts JSON to JS object
console.log(users); // [{nom: "Alice"}, ...]
// Send data (POST)
await fetch('/api/contact', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ nom: "Alice" }) // object JS → texte JSON
});
Customize it
- Always check
response.ok before reading data (error handling)
- Use
async/await (readable) instead of .then() (chaining)
- Test your APIs with DevTools (Network tab) to see requests
In plain terms: It's a drawer in the browser. You store info in it (dark theme, cart, preferences). It persists even after closing the browser. Unlike cookies, this data never goes to the server. It's local storage, simple and free. Perfect for remembering visitor choices without a database.
// Save a value
localStorage.setItem('theme', 'sombre'); // key → value (text)
// Read a value
const theme = localStorage.getItem('theme'); // returns "dark"
// Store an object (convert to JSON first)
const prefs = { theme: 'sombre', lang: 'fr' };
localStorage.setItem('prefs', JSON.stringify(prefs));
// Relire l'object
const saved = JSON.parse(localStorage.getItem('prefs'));
Customize it
- Limit: ~5 MB per domain. Don't store heavy files
- Use
sessionStorage if you want data to disappear when the tab closes
- Never store passwords or sensitive tokens in localStorage
In plain terms: ES6 is the modern version of JavaScript (released in 2015). Before, code was verbose and hard to read. Arrow functions (=>) shorten functions. Destructuring extracts values from an object in one line. Template literals (`...`) let you insert variables into text. It's cleaner, shorter, more readable.
// Arrow function (instead of function)
const saluer = (nom) => `Salut ${nom} !`; // template literal
// Destructuring: extract values
const { nom, age } = user; // instead of user.nom, user.age
// Spread: merge objects/arrays
const newUser = { ...user, role: "admin" }; // copy + add
// Optional chaining: avoid errors
const ville = user?.adresse?.ville; // undefined if missing
Customize it
- Adopt arrows
=> for short callbacks (map, filter, forEach)
- Use backticks
`text ${variable}` instead of concatenation +
?. (optional chaining) prevents crashes when data is missing
04
Responsive & Mobile — Adaptation
6 fundamentals for making your site display perfectly on all screens
In plain terms: The viewport is the visible screen size. Without this meta tag, mobile displays your page as a miniature desktop version. Unreadable. This single line tells the browser: "adapt the width to the phone screen." It's the very first thing to put in the <head> of every page. Without it, no responsive design works.
<!-- In the <head> of EVERY HTML page -->
<meta name="viewport"
content="width=device-width, initial-scale=1.0">
<!-- width=device-width → width = real screen -->
<!-- initial-scale=1.0 → no initial zoom -->
<!-- WITHOUT this tag: mobile displays 980px wide -->
<!-- WITH this tag: mobile displays 375px (its real size) -->
Customize it
- Never add
maximum-scale=1: it prevents accessibility zoom
- Place this tag right after
<meta charset="UTF-8">
- Check in DevTools (responsive mode) that your page adapts properly
In plain terms: Mobile-first means building your house starting with the studio apartment. You design for the smallest screen first (mobile), then add space and columns for larger screens. It's simpler than the reverse: adding is easy, removing is painful. And 60% of web traffic is mobile today.
/* Base CSS = mobile (no media query) */
.container {
padding: 16px;
font-size: 14px; /* readable size on small screen */
}
/* We ADD rules for larger screens */
@media (min-width: 768px) { /* tablet and up */
.container { max-width: 720px; margin: 0 auto; }
}
Customize it
- Always write mobile CSS first (outside media queries)
- Use
min-width to progressively add (not max-width)
- Test by shrinking the window: if it breaks, it's not mobile-first
In plain terms: A breakpoint is the screen size where your design changes shape. Like a folding door: from a certain width, it unfolds. You don't need 15 breakpoints. Three are enough: mobile, tablet, desktop. Choose them based on your content, not a specific phone model.
/* 3 breakpoints are enough for 95% of sites */
/* Mobile: 0 - 639px (no media query = base CSS) */
/* Tablet: from 640px */
@media (min-width: 640px) {
.grid { grid-template-columns: 1fr 1fr; }
}
/* Desktop: from 1024px */
@media (min-width: 1024px) {
.grid { grid-template-columns: repeat(3, 1fr); }
}
Customize it
- Classic breakpoints:
640px, 768px, 1024px, 1280px
- Add a breakpoint only when the content requires it (not systematically)
- Use DevTools to find where your design "breaks" naturally
In plain terms: Sending a 4K image to a phone is like delivering a couch by helicopter. It works, but it's wasteful. srcset gives the browser a list of sizes. It picks the best one for the screen. WebP format is 30% lighter than JPEG. Result: faster pages, especially on 4G.
<!-- The browser chooses the image based on screen width -->
<img src="photo-800.webp"
srcset="photo-400.webp 400w,
photo-800.webp 800w,
photo-1200.webp 1200w"
sizes="(max-width: 640px) 100vw, 50vw"
alt="Photo description"
loading="lazy"> <!-- loads the image only when visible -->
Customize it
- Generate 3 sizes per image: 400px, 800px, 1200px wide
- Use WebP (or AVIF) format for 30-50% lighter files
- Add
loading="lazy" to all images below the fold
In plain terms: On a phone, you tap with your finger, not with a pixel-precise mouse cursor. A 20px button is too small: you miss one out of two. The rule: minimum 44x44 pixels for every clickable area. That's the size of a fingertip. Also think about spacing between buttons to avoid accidental taps.
/* Minimum recommended size for touch */
.btn {
min-height: 44px; /* Apple/Google standard */
min-width: 44px;
padding: 12px 24px; /* comfortable area */
}
/* Spacing between buttons to avoid errors */
.btn + .btn {
margin-left: 12px; /* space between two buttons */
}
Customize it
- Minimum
44px for links, buttons, checkboxes, and clickable icons
- Add
padding rather than margin to enlarge the click area
- Test on a real phone: if you miss the button, it's too small
In plain terms: DevTools' responsive mode is a simulation. Not reality. A real phone has a different screen, different browser, different bandwidth. If you don't test on a real device, you'll discover bugs in production. Test at minimum on an iPhone and an Android. Use your phone's Wi-Fi to see your local site.
<!-- 1. Find your computer's local IP -->
<!-- Mac : ifconfig | grep "inet " -->
<!-- Windows : ipconfig | findstr IPv4 -->
<!-- 2. Start a local server -->
<!-- python3 -m http.server 8080 -->
<!-- 3. On your phone (same Wi-Fi), open: -->
<!-- http://192.168.1.42:8080 -->
<!-- 4. Test: scroll, tap, forms, rotation -->
Customize it
- Test on at least 3 devices: small phone, large phone, tablet
- Check rotation (portrait/landscape) on each device
- Use
BrowserStack or LambdaTest if you don't have multiple devices
05
Accessibility — Inclusion
6 fundamentals for making your site usable by everyone
In plain terms: A screen reader doesn't see your page. It reads it. If you use <div> everywhere, it doesn't know where the menu is, where the content is. Semantic tags (<nav>, <main>, <button>) are signposts for blind users, visually impaired users, and bots. A <button> is keyboard-clickable. A <div> is not.
<!-- BAD: all divs, nothing is accessible -->
<div onclick="menu()">Menu</div>
<div class="title">My article</div>
<!-- GOOD: semantic tags, natively accessible -->
<button type="button">Menu</button> <!-- focusable + keyboard-activable -->
<h1>My article</h1> <!-- recognized as main heading -->
<nav aria-label="Main menu"> <!-- identifies navigation -->
<a href="/home">Home</a>
</nav>
Customize it
- Replace every
<div onclick> with a real <button>
- Structure your headings hierarchically:
h1 > h2 > h3 (don't skip levels)
- Add
aria-label to <nav> when you have multiple ones
In plain terms: ARIA is a translator for screen readers. When HTML alone isn't enough (a menu that opens, an active tab, a loading spinner), ARIA adds invisible information. aria-expanded tells if a menu is open or closed. role="alert" announces an important message. But the golden rule: if a native HTML tag is enough, use it. ARIA is a complement, not a replacement.
<!-- Dropdown menu: indicate open/closed state -->
<button aria-expanded="false"
aria-controls="menu">Menu</button>
<ul id="menu" hidden>...</ul>
<!-- Alert message announced automatically -->
<div role="alert">Error: invalid email</div>
<!-- Loading zone -->
<div aria-busy="true" aria-live="polite">
Loading...
</div>
Customize it
- First ARIA rule: only use ARIA if native HTML isn't enough
- Update
aria-expanded in JavaScript when the menu opens/closes
- Use
aria-live="polite" for dynamically changing areas
In plain terms: Imagine reading light gray text on a white background in bright sunlight. Impossible. The 4.5:1 ratio guarantees everyone can read, even in poor conditions. This ratio compares text brightness with background brightness. Tools calculate it for you. If it's below 4.5, change your color. It's a legal requirement in many countries.
/* BAD contrast: light gray on white (ratio ~2:1) */
.texte-illisible {
color: #b0b0b0; /* too light on white background */
background: #ffffff;
}
/* GOOD contrast: ratio 7:1+ (AAA level) */
.texte-lisible {
color: #1a1a2e; /* dark on light background */
background: #ffffff; /* ratio 15.4:1 */
}
Customize it
- Normal text: minimum ratio
4.5:1 (AA). Large text (18px+): 3:1
- Free tool:
webaim.org/resources/contrastchecker
- Also check buttons, links, and placeholders (often too light)
In plain terms: Some people don't use a mouse. They navigate with Tab (next element), Shift+Tab (previous) and Enter (activate). If your site only works with a mouse, you're excluding these users. Visible focus (the blue outline) is essential: it's the keyboard version of the cursor arrow. Never hide it. If you create a clickable element, it must be focusable.
/* NEVER: don't hide visible focus */
*:focus { outline: none; } /* forbidden! */
/* GOOD: customize it nicely */
:focus-visible {
outline: 2px solid #6366f1; /* visible and aesthetic */
outline-offset: 2px; /* small gap around */
}
/* :focus-visible = keyboard only (not on mouse click) */
Customize it
- Test by navigating with Tab: every interactive element should receive focus
- Use
:focus-visible (not :focus) to target keyboard navigation only
- Add
tabindex="0" to custom clickable elements (if you can't use <button>)
In plain terms: A screen reader (VoiceOver, NVDA) reads your page aloud. It announces headings, links, buttons, images. If an image has no alt, it just says "image" with no explanation. If a button has no text, it just says "button." Every interactive element must have a name that makes sense when heard. Think: "does this make sense if I hear it without seeing it?"
<!-- Informative image: describe the content -->
<img src="graph.png" alt="Sales up 25% in March">
<!-- Decorative image: empty alt (reader ignores it) -->
<img src="deco.svg" alt="">
<!-- Button with icon only: aria-label required -->
<button aria-label="Close">×</button>
<!-- Content visible only to screen readers -->
<span class="sr-only">Open menu</span>
Customize it
- Every
<img> must have an alt: descriptive (informative) or empty (decorative)
- Test with VoiceOver (Mac: Cmd+F5) or NVDA (Windows, free)
- The
.sr-only class hides visually but remains readable by screen readers
In plain terms: Your visitor may have enabled dark mode, reduced animations, or enlarged text in their system settings. CSS can detect these preferences and adapt automatically. prefers-color-scheme detects dark mode. prefers-reduced-motion detects people sensitive to animations. Respecting these choices is digital courtesy.
/* Detect system dark mode */
@media (prefers-color-scheme: dark) {
body { background: #0a0a0f; color: #e4e4e7; }
}
/* Reduce animations if the user requests it */
@media (prefers-reduced-motion: reduce) {
* { animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important; }
}
Customize it
- Use CSS variables (
--bg, --text) to switch colors easily
- Test: Mac > Settings > Appearance > Dark. Windows > Settings > Colors
- Add
prefers-contrast: high for users who force high contrast
06
SEO & Performance — Visibility
6 fundamentals for getting found by Google and loading fast
In plain terms: Google doesn't see your page like you do. It reads the source code. Meta tags are your ID card for search engines. The <title> is the name on your mailbox. The meta description is the note stuck on it. Without them, Google makes up a summary on its own. And it's rarely pretty.
<head>
<!-- Title: 50-60 characters, main keyword first -->
<title>Recettes faciles | MonSite</title>
<!-- Description: 150-160 characters, makes you want to click -->
<meta name="description"
content="Decouvre 50 recettes faciles a preparer en 15 min.">
<!-- Indicates the official URL (avoids duplicate content) -->
<link rel="canonical" href="https://monsite.fr/recettes">
</head>
Customize it
- Every page must have a unique
<title> and meta description
- Place your main keyword at the beginning of the title, not the end
- Add Open Graph tags (
og:title, og:image) for a nice preview on social media
In plain terms: H1 to H6 headings are your page's table of contents. Google reads it to understand the topic. One H1 per page (the main title). Then H2s for sections, H3s for subsections. Like a book: one cover title, multiple chapters, multiple paragraphs per chapter. Never skip levels (no H1 then H4).
<!-- GOOD: logical hierarchy, one H1 -->
<h1>Gardening guide</h1> <!-- unique title -->
<h2>Essential tools</h2> <!-- section -->
<h3>Pruning shears</h3> <!-- subsection -->
<h3>The spade</h3> <!-- subsection -->
<h2>Planter ses legumes</h2> <!-- new section -->
<!-- BAD: duplicate H1, skipped levels -->
<h1>Jardinage</h1> <h1>Outils</h1> <h4>Beche</h4>
Customize it
- One
<h1> per page. It's the main title, not the logo
- Don't choose a heading level for text size. Use CSS for that
- Check your hierarchy with the "HeadingsMap" Chrome extension
In plain terms: Google measures 3 things on your page. LCP: how long before the largest element appears (your hero image, for example). FID: how long before the site responds to the first click. CLS: does the page shift by itself during loading. It's like a restaurant: the dish arrives fast (LCP), the waiter responds fast (FID), and the table doesn't move (CLS).
<!-- LCP: load the main image with priority -->
<img src="hero.webp" fetchpriority="high" alt="Hero">
<!-- CLS: reserve space for images -->
<img src="photo.webp" width="800" height="450" alt="Photo">
<!-- FID: load heavy JS deferred -->
<script src="app.js" defer></script>
/* Objectifs : LCP < 2.5s | FID < 100ms | CLS < 0.1 */
Customize it
- Test with PageSpeed Insights (web.dev/measure) to see your scores
- Add
width and height to all your images to prevent layout shifts (CLS)
- Put
defer on all your non-critical scripts
In plain terms: A 5 MB image is like sending a 10 kg package by mail for a 200 g item. WebP format compresses better than JPEG (30% lighter). Lazy loading tells the browser: "load this image only when the visitor reaches it." Less weight = faster page = happier visitors = happier Google.
<!-- Lazy loading: loads the image when it enters the viewport -->
<img src="photo.webp" loading="lazy" alt="Paysage">
<!-- Modern format with JPEG fallback -->
<picture>
<source srcset="photo.avif" type="image/avif">
<source srcset="photo.webp" type="image/webp">
<img src="photo.jpg" alt="Paysage">
</picture>
Customize it
- Convert all your images to WebP with Squoosh (squoosh.app, free)
- Put
loading="lazy" on all images except those visible on first screen
- Resize before uploading: you don't need a 4000px image for an 800px display
In plain terms: Imagine preparing a meal. You take out ingredients from the fridge before cooking, not at the last minute. preload tells the browser: "this resource is urgent, download it right away." prefetch says: "the visitor will probably go to this page next, prepare it in the background." Good timing makes all the difference.
<head>
<!-- Preload: loads the font BEFORE it is used -->
<link rel="preload" href="font.woff2"
as="font" type="font/woff2" crossorigin>
<!-- Prefetch: prepares the next page in the background -->
<link rel="prefetch" href="/page-suivante.html">
<!-- Preconnect: opens connection to external server -->
<link rel="preconnect" href="https://fonts.googleapis.com">
</head>
Customize it
- Use
preload for the main font and hero image (LCP)
- Use
preconnect for external domains (Google Fonts, API, CDN)
- Don't overuse: too many preloads slow things down instead of speeding up (3-4 max)
In plain terms: Your CSS and JS code contains spaces, line breaks, comments. Useful for you, but unnecessary for the browser. Minification removes all of that. It's like compressing a suitcase: same content, less space. Result: lighter files, faster download, happier visitors.
/* BEFORE minification (readable, 120 bytes) */
.btn {
background: #6366f1;
padding: 12px 24px;
border-radius: 8px;
}
/* AFTER minification (unreadable, 52 bytes) */
.btn{background:#6366f1;padding:12px 24px;border-radius:8px}
Customize it
- Use cssnano (CSS) and Terser (JS) to minify automatically
- Always keep the original file (style.css) and the minified file (style.min.css)
- In production, link the
.min.css file. In dev, the normal file
07
Tools & Workflow — Productivity
5 fundamentals for working fast, well, and stress-free
In plain terms: A code editor is your workshop. VS Code is free, lightweight, and ultra-popular. It colors your code, completes your tags, detects errors in real time. It's the difference between writing by hand on blank paper and writing with a pen that corrects spelling. Install the right extensions and you save hours every week.
// Essential extensions for VS Code:
// 1. Live Server — reloads the page on every save
// 2. Prettier — formats your code automatically
// 3. ESLint — detects JavaScript errors
// 4. Auto Rename Tag — renames the closing tag
// Magic shortcut: Ctrl+Shift+P (command palette)
// Type "format" to format your file in 1 second
Customize it
- Enable "Format on Save" in settings (no need to think about it)
- Type
! then Tab in an HTML file: VS Code generates the complete skeleton
- Use Emmet:
ul>li*5 + Tab creates a list of 5 items at once
In plain terms: Git is a supercharged Ctrl+Z for your project. Each "commit" is a snapshot of your code at a moment in time. You can go back, compare versions, work with others without overwriting each other's work. Without Git, a file deleted by mistake is lost forever. With Git, you travel back in time with one command.
# Initialize Git in your project
git init
# Add all modified files
git add .
# Save with a clear message
git commit -m "Added contact page"
# View your save history
git log --oneline
Customize it
- Make a commit at each functional milestone ("contact page OK", "mobile menu OK")
- Use GitHub (free) to store your code online (cloud backup)
- VS Code integrates Git: you can commit, push, and view diffs without leaving the editor
In plain terms: DevTools is an X-ray for your web page. You see the bones (HTML), the clothes (CSS), the reflexes (JS). You click an element, you see its code, styles, margins. You modify live to test. Nothing is saved: it's a sandbox. All browsers have it. Shortcut: F12 or right-click > Inspect.
/* Essential DevTools tabs */
// Elements: view and modify HTML/CSS live
// Console: display JS errors and test code
// Network: see every downloaded file (size, time)
// Lighthouse: run a full audit (perf, SEO, a11y)
// Tip: in Console, type document.title
// to display the current page title
Customize it
- Shortcut:
Ctrl+Shift+C (Windows) or Cmd+Shift+C (Mac) to inspect an element
- Network tab > check "Disable cache" during dev (avoids stale files)
- Use Responsive mode (smartphone icon) to simulate a mobile without having one
In plain terms: Your site works on your computer. Great. But nobody else can see it. Deployment is moving your site from your PC to a server accessible to all. Free services like Netlify or Vercel do it in 2 minutes. You connect your GitHub repo, and on every push, the site updates automatically. Zero config, zero stress.
# Method 1: Netlify (drag and drop)
# → Go to app.netlify.com
# → Drag your project folder onto the page
# → Your site is online in 30 seconds
# Method 2: Vercel (via GitHub)
# → Connect your GitHub repo on vercel.com
# → Every git push updates the site
# → Free URL: myproject.vercel.app
Customize it
- Netlify : ideal for static sites (HTML/CSS/JS). Drop & deploy
- Vercel : ideal if you use a framework (Next.js, SvelteKit)
- GitHub Pages : also free, perfect for a portfolio or documentation
In plain terms: A domain name is your house address (mysite.com). Hosting is the land your house is built on. Without a domain, people type a cryptic IP. Without hosting, your house doesn't exist anywhere. A domain costs 5-15 euros/year. Basic hosting, 3-5 euros/month. It's the price of a coffee to be visible on the Internet.
/* Choosing a good domain name */
// Short: mysite.com (not my-super-recipes-site-2026.com)
// Memorable: easy to spell over the phone
// Extension: .fr for France, .com for international
/* Recommended hosts (beginner) */
// OVH — French, domain + hosting, ~3 EUR/mois
// Hostinger — affordable, simple interface, ~2 EUR/mois
// Infomaniak — Swiss, eco-friendly, good support
Customize it
- Check your domain availability on namecheap.com or ovh.com
- Get the .fr AND .com if possible (prevents someone else from taking it)
- Free hosting (Netlify, Vercel) is enough to start. Switch to paid when you have traffic
08
Web Security — Protection
6 fundamentals for protecting your site and visitors from attacks
In plain terms: It's the padlock in your address bar. Without it, everything your visitor types (password, email) travels in plain text over the network. Like a postcard everyone can read. With HTTPS, data is encrypted: even if someone intercepts the message, they only see gibberish. Google penalizes sites without HTTPS. Browsers display "Not secure."
<!-- Force HTTPS in HTML -->
<meta http-equiv="Content-Security-Policy"
content="upgrade-insecure-requests">
# Force HTTPS in .htaccess (Apache)
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Free certificate: Let's Encrypt (automatic)
Customize it
- Most hosts offer a free SSL certificate (Let's Encrypt)
- Make sure ALL your resources (images, scripts) are also served over HTTPS
- Test with SSL Labs (ssllabs.com) to verify your certificate is correct
In plain terms: It's the guest list for your party. Only authorized scripts can enter. Others are blocked at the door. CSP (Content Security Policy) tells the browser: "accept scripts from my domain and Google Fonts, but block everything else." If a hacker injects a malicious script, the browser refuses it because it's not on the list.
<!-- CSP in HTML (meta tag) -->
<meta http-equiv="Content-Security-Policy"
content="
default-src 'self';
script-src 'self' https://cdn.exemple.com;
style-src 'self' https://fonts.googleapis.com;
img-src 'self' data: https://stats.optimetier.fr https:;
font-src 'self' https://fonts.gstatic.com;
">
Customize it
'self' = your own domain. Add the external CDNs you use
- Start strict, then allow case by case (better than opening everything)
- Test with the Console tab in DevTools: CSP violations are displayed there
In plain terms: Imagine someone sticking a fake sign on your storefront. Visitors believe it's real. XSS is the same: fake code injected into your page. A hacker writes JavaScript in a form field. If you display that text as-is, the code executes for all visitors. It can steal cookies, redirect to a fake site, or display misleading content.
// DANGER: injecting raw text into HTML
element.innerHTML = texteUtilisateur; // XSS possible!
// SECURE: use textContent (no HTML interpreted)
element.textContent = texteUtilisateur;
// If you must display HTML, escape the characters
function escape(str) {
return str.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
}
Customize it
- Golden rule: never use
innerHTML with user data
- Prefer
textContent (plain text) or createElement (clean DOM)
- Server-side too: escape with
htmlspecialchars() in PHP
In plain terms: Imagine a trap site sends a form to your bank on your behalf, without you knowing. CSRF (Cross-Site Request Forgery) is exactly that: a fake order sent with your credentials. The solution: a unique, secret token in every form. The server verifies the token is valid. If someone submits the form from another site, they don't have the token. Request denied.
<!-- The server generates a unique token per session -->
<form method="POST" action="/transfert">
<input type="hidden" name="csrf_token"
value="a8f3b9e1c7d2...">
<input type="text" name="montant">
<button type="submit">Submit</button>
</form>
<!-- The server verifies: received token == session token -->
Customize it
- Generate a random token for each session (never the same twice)
- Also check the
Origin header to confirm the request comes from your site
- Modern frameworks (Laravel, Django, Express) handle CSRF automatically
In plain terms: Every form field is an entry point. A visitor can type anything: code, commands, weird characters. Sanitization is the guard searching bags at the entrance. You clean every piece of data before using it. You strip HTML tags, limit length, verify the format. Client-side AND server-side. Always both.
// Client-side: validate BEFORE sending
const email = input.value.trim();
if (!email.includes('@')) {
alert('Invalid email');
}
// Server-side (PHP): ALWAYS re-verify
$email = filter_var($_POST['email'], FILTER_SANITIZE_EMAIL);
$nom = htmlspecialchars($_POST['nom'], ENT_QUOTES);
// strip_tags() removes all HTML tags
Customize it
- Client-side validation is for comfort. Server-side validation is for security
- Use HTML attributes:
type="email", required, maxlength, pattern
- Never make SQL queries with raw data. Use prepared statements
In plain terms: Security headers are instructions your server gives to the browser. "Don't embed my page in an iframe" (clickjacking). "Don't send the full referrer" (privacy). "Force HTTPS for 1 year" (HSTS). It's like putting an alarm, a lock, and a camera on your house. Each header blocks a specific type of attack.
# Security headers (.htaccess Apache)
# Prevents display in an iframe (anti-clickjacking)
Header set X-Frame-Options "DENY"
# Forces HTTPS for 1 year
Header set Strict-Transport-Security "max-age=31536000"
# Prevents MIME type sniffing
Header set X-Content-Type-Options "nosniff"
# Limits info sent to other sites
Header set Referrer-Policy "strict-origin-when-cross-origin"
Customize it
- Test your headers on securityheaders.com (goal: A or A+ grade)
- Add
Permissions-Policy to block camera, microphone, geolocation if you don't need them
- Combine with CSP (topic 44) for maximum protection
Checklist before publishing
Check each item before putting your site online. 48 fundamentals summarized in 48 checkboxes.
Effects.lab
Thanks for downloading
this guide!
You now have the 48 fundamentals for building solid, fast, and secure websites. Here is a summary of the 8 chapters:
| # |
Chapter |
Topics |
| 01 | HTML — Structure | 01-06 |
| 02 | CSS — Styling | 07-13 |
| 03 | JavaScript — Interactivity | 14-19 |
| 04 | Responsive — Adaptation | 20-25 |
| 05 | Accessibility — Inclusion | 26-31 |
| 06 | SEO & Performance — Visibility | 32-37 |
| 07 | Tools & Workflow — Productivity | 38-42 |
| 08 | Web Security — Protection | 43-48 |
Discover Effect.Labs — 664 effects | 26 templates | 20 categories
Premium CSS & JS effects library to speed up your web projects
Effect.Labs — 2026 — All rights reserved
This guide is free. Share it freely.