Modern HTML and CSS
HTML should first describe the meaning and structure of content, while CSS controls its presentation. Solid semantic HTML, normal flow, Flexbox, Grid and responsive design are enough for most ordinary websites without immediately adding a framework.
Related topics: JavaScript, UX and Accessibility, Browser DevTools and Web Performance.
A practical reference for building modern, semantic, responsive and accessible websites.
HTML describes the structure and meaning of content.
CSS controls appearance, layout and presentation.
JavaScript adds logic and interaction when HTML and CSS are not enough.
Table of contents
- HTML basics
- Document structure
- HTML semantics
- Text, lists and links
- Images and media
- Tables
- Forms
- Accessibility
- CSS basics
- Selectors
- Cascade, inheritance and specificity
- Box model
- Units
- Colours and CSS variables
- Typography
- Normal flow
- Flexbox
- CSS Grid
- Positioning
- Responsive design
- Container Queries
- Modern CSS
- Dark mode
- Animations
- Styling forms
- CSS reset / base
- Project organisation
- Complete page example
- Good practices
- Cheat sheet
- What you should know
1. HTML basics
HTML is not for drawing a page. Its job is to describe what each piece of content is.
<article class="product">
<h2>Example Laptop</h2>
<p>Lightweight business notebook.</p>
<a href="/prestige-13">View product</a>
</article>
This tells the browser and assistive technology that the block is a self-contained piece of content with a heading, description and link.
CSS defines the presentation:
.product {
padding: 2rem;
border-radius: 1rem;
background: white;
}
.product h2 {
font-size: 1.5rem;
}
Prefer class names that describe purpose rather than appearance.
Better:
<h2 class="product-title">Example notebook</h2>
instead of:
<div class="big-red-text">Example notebook</div>
2. Document structure
A minimal modern HTML document:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
<title>My website</title>
<meta
name="description"
content="Short description of the page."
>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>My website</h1>
</body>
</html>
<!doctype html>
Tells the browser to use modern HTML rendering. It should be the first line of the file.
<html lang="en">
The root element of the document.
The lang attribute helps screen readers, search engines, automatic translation and spell checking.
<head>
Contains document metadata.
Encoding:
<meta charset="utf-8">
Viewport:
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
Title:
<title>Example Site - stories</title>
Description:
<meta
name="description"
content="Horror, weird fiction and science-fiction stories."
>
CSS:
<link rel="stylesheet" href="/css/style.css">
Favicon:
<link rel="icon" href="/favicon.svg" type="image/svg+xml">
3. HTML semantics
A modern page should not be built only from div elements.
Typical structure:
<body>
<header>
...
</header>
<nav>
...
</nav>
<main>
...
</main>
<footer>
...
</footer>
</body>
<header>
Header of a page or section.
<header>
<h1>Example Site</h1>
<p>Stories from places that do not exist.</p>
</header>
A document may contain more than one header.
<main>
The main content of the document. Usually there should be one primary main.
<nav>
Navigation:
<nav aria-label="Main navigation">
<a href="/">Home</a>
<a href="/stories">Stories</a>
<a href="/cycles">Series</a>
<a href="/about">About</a>
</nav>
<section>
A thematic section:
<section>
<h2>Latest stories</h2>
</section>
Do not use section as an automatic replacement for div.
<article>
Independent content that could stand on its own:
<article>
<h2>The House on Gray Street</h2>
<p>...</p>
</article>
Typical uses include blog posts, articles, product cards, news items, comments and stories.
<aside>
Secondary or complementary content.
<footer>
Footer for a document or section.
<div>
A neutral container. Use it when you need an element for layout or styling and no semantic element fits.
4. Text, lists and links
Headings
HTML has six heading levels:
<h1>...</h1>
<h2>...</h2>
<h3>...</h3>
<h4>...</h4>
<h5>...</h5>
<h6>...</h6>
Headings describe content hierarchy, not visual size.
Paragraphs
<p>This is a paragraph.</p>
Emphasis
<strong>very important</strong>
<em>really</em>
strong expresses importance. em expresses emphasis.
Quotes
Inline:
<p>The author wrote <q>this is a quote</q>.</p>
Block quote:
<blockquote>
<p>Quoted text.</p>
</blockquote>
Code
Inline:
<code>sudo apt update</code>
Block:
<pre><code>sudo apt update
sudo apt upgrade</code></pre>
Lists
Unordered:
<ul>
<li>Debian</li>
<li>FreeBSD</li>
<li>OpenBSD</li>
</ul>
Ordered:
<ol>
<li>Install the system</li>
<li>Update packages</li>
<li>Configure nginx</li>
</ol>
Definition list:
<dl>
<dt>HTML</dt>
<dd>Document structure.</dd>
<dt>CSS</dt>
<dd>Document presentation.</dd>
</dl>
Links
<a href="/about">About us</a>
External link:
<a href="https://example.com">Example</a>
New tab:
<a
href="https://example.com"
target="_blank"
rel="noopener"
>
Example
</a>
Email:
<a href="mailto:test@example.com">Write to us</a>
Anchor:
<a href="#contact">Contact</a>
<section id="contact">
<h2>Contact</h2>
</section>
Link vs button
Links navigate:
<a href="/products">Products</a>
Buttons perform actions:
<button type="button">Open menu</button>
Avoid clickable div elements when the element is really a button.
5. Images and media
Basic image:
<img
src="/images/castle.jpg"
alt="Castle ruins on a hill"
>
The alt attribute describes the meaning of the image.
Decorative image:
<img
src="/images/decoration.svg"
alt=""
>
Provide dimensions when possible:
<img
src="photo.jpg"
alt="Old house"
width="1200"
height="800"
>
Responsive baseline:
img {
max-width: 100%;
height: auto;
}
srcset
<img
src="photo-800.jpg"
srcset="
photo-480.jpg 480w,
photo-800.jpg 800w,
photo-1600.jpg 1600w
"
sizes="
(max-width: 700px) 100vw,
800px
"
alt="City view"
>
The browser chooses the most appropriate file.
<picture>
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img
src="hero.jpg"
alt="Mountain landscape"
>
</picture>
Lazy loading
<img
src="photo.jpg"
alt="..."
loading="lazy"
>
Do not automatically lazy-load the main above-the-fold/LCP image.
figure
<figure>
<img
src="server.jpg"
alt="Server in a rack cabinet"
>
<figcaption>
Home FreeBSD server.
</figcaption>
</figure>
6. Tables
Tables are for tabular data.
<table>
<caption>Server prices</caption>
<thead>
<tr>
<th scope="col">Server</th>
<th scope="col">RAM</th>
<th scope="col">Price</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">VPS-1</th>
<td>4 GB</td>
<td>20 EUR</td>
</tr>
<tr>
<th scope="row">VPS-2</th>
<td>8 GB</td>
<td>40 EUR</td>
</tr>
</tbody>
</table>
Do not use tables for page layout.
7. Forms
Basic form:
<form action="/contact" method="post">
<label for="name">
Name
</label>
<input
id="name"
name="name"
type="text"
>
<button type="submit">
Send
</button>
</form>
name defines the field name sent to the backend.
id lets you associate the control with its label.
Input types
<input type="text">
<input type="email">
<input type="password">
<input type="number">
<input type="date">
<input type="checkbox">
<input type="radio">
<input type="file">
<input type="url">
<input type="search">
Validation
Required field:
<input
type="email"
name="email"
required
>
Length:
<input type="text" minlength="3" maxlength="100">
Range:
<input type="number" min="1" max="100">
Pattern:
<input type="text" pattern="[0-9]{6}">
HTML validation does not replace backend validation.
textarea
<label for="message">Message</label>
<textarea
id="message"
name="message"
rows="8"
></textarea>
select
<label for="country">Country</label>
<select id="country" name="country">
<option value="">Choose</option>
<option value="pl">Poland</option>
<option value="de">Germany</option>
<option value="cz">Czechia</option>
</select>
fieldset
<fieldset>
<legend>Account type</legend>
<label>
<input
type="radio"
name="account"
value="private"
>
Personal
</label>
<label>
<input
type="radio"
name="account"
value="business"
>
Business
</label>
</fieldset>
autocomplete
<input type="text" name="name" autocomplete="name">
<input type="email" autocomplete="email">
<input type="password" autocomplete="current-password">
details
Expandable content without JavaScript:
<details>
<summary>More information</summary>
<p>Additional content goes here.</p>
</details>
data-*
<article
data-product-id="123"
data-category="laptop"
>
JavaScript can read these values later.
8. Accessibility
Accessibility starts with correct HTML.
Prefer:
<button>Open</button>
instead of:
<div role="button">Open</div>
Prefer a labelled field:
<label for="email">Email</label>
<input id="email" type="email">
ARIA
ARIA is useful when native HTML is not enough.
<nav aria-label="Main menu">
<button
aria-expanded="false"
aria-controls="main-menu"
>
Menu
</button>
General rule:
If an appropriate native HTML element exists, use it instead of recreating its behaviour with ARIA.
9. CSS basics
Attach a stylesheet:
<link rel="stylesheet" href="/css/style.css">
Syntax:
selector {
property: value;
}
Example:
h1 {
color: navy;
font-size: 3rem;
}
10. Selectors
Element:
p {}
Class:
.card {}
ID:
#header {}
Attribute:
input[type="email"] {}
Multiple selectors:
h1,
h2,
h3 {
font-family: sans-serif;
}
Descendant:
article p {}
Direct child:
article > p {}
Adjacent sibling:
h2 + p {}
General sibling:
h2 ~ p {}
Pseudo-classes
a:hover {
color: red;
}
button:focus-visible {
outline: 3px solid blue;
}
li:first-child {}
li:last-child {}
li:nth-child(even) {}
:not()
button:not(.primary) {
opacity: 0.8;
}
:is()
article :is(h1, h2, h3) {
color: navy;
}
:where()
:where(article, section, aside) p {
line-height: 1.6;
}
:where() adds no selector specificity.
:has()
.card:has(img) {
padding-top: 0;
}
.field:has(input:invalid) {
border-color: red;
}
body:has(dialog[open]) {
overflow: hidden;
}
Pseudo-elements
.external-link::after {
content: " ↗";
}
Most common:
::before
::after
11. Cascade, inheritance and specificity
CSS means Cascading Style Sheets.
When several declarations target the same property, the browser determines which one wins.
p {
color: red;
}
.text {
color: blue;
}
For:
<p class="text">
the class rule wins.
The result is affected by origin, !important, cascade layers, specificity and source order.
Simplified specificity order:
element < class < id
Avoid deeply coupled selectors such as:
body main section article div.card p.description {}
Prefer:
.card-description {}
Use !important only when justified. If you need it everywhere, the CSS architecture probably needs work.
Typography-related properties often inherit:
body {
color: #222;
font-family: sans-serif;
}
Properties such as margin, padding and border normally do not.
12. Box model
Every element is a box:
margin
└── border
└── padding
└── content
Useful baseline:
*,
*::before,
*::after {
box-sizing: border-box;
}
With border-box, declared width includes padding and border.
Margin shorthand:
margin: 2rem;
margin: 1rem 2rem;
margin: 1rem 2rem 3rem 4rem;
Order:
top right bottom left
Logical properties are often preferable:
margin-inline: 2rem;
margin-block: 1rem;
padding-inline: 1rem;
padding-block: 1rem;
Related logical properties include border-inline, border-block, inline-size and block-size.
13. Units
px
Useful for things such as thin borders.
border: 1px solid;
rem
Useful for spacing, typography and component sizing.
padding: 2rem;
em
Relative to the current font size.
button {
padding: 0.7em 1.2em;
}
Percentages
width: 50%;
Viewport units
width: 100vw;
min-height: 100vh;
Modern mobile-friendly variants:
svh
lvh
dvh
Example:
.hero {
min-height: 100dvh;
}
min()
width: min(100%, 1200px);
max()
width: max(50%, 400px);
clamp()
Fluid typography:
h1 {
font-size: clamp(2rem, 5vw, 5rem);
}
This means minimum 2rem, preferred 5vw and maximum 5rem.
calc()
width: calc(100% - 2rem);
height: calc(100vh - 80px);
14. Colours and CSS variables
Hex:
color: #ff0000;
Modern RGB:
color: rgb(255 0 0);
color: rgb(255 0 0 / 50%);
HSL:
color: hsl(0 100% 50%);
currentColor
.button {
color: blue;
border: 1px solid currentColor;
}
color-mix()
background:
color-mix(
in srgb,
var(--primary) 20%,
white
);
Custom properties
:root {
--color-bg: #fff;
--color-text: #111;
--color-primary: #4058d6;
--space-small: 0.5rem;
--space-medium: 1rem;
--space-large: 2rem;
}
Use:
body {
background: var(--color-bg);
color: var(--color-text);
}
Fallback:
color: var(--link-color, blue);
Variables can be overridden locally.
15. Typography
Typical baseline:
body {
font-family:
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
sans-serif;
font-size: 1rem;
line-height: 1.6;
color: #222;
}
Very wide paragraphs are hard to read:
.article {
max-width: 70ch;
}
The ch unit is useful for readable text widths.
16. Normal flow
HTML already has a default document flow.
Block elements such as div, p, section and article normally stack vertically.
Before reaching for Grid, Flexbox or position, check whether normal flow already solves the problem.
17. Flexbox
Flexbox is mainly for one-dimensional layout.
.navigation {
display: flex;
gap: 1rem;
}
Default direction:
flex-direction: row;
Vertical:
flex-direction: column;
Main-axis alignment:
justify-content: center;
justify-content: space-between;
justify-content: space-around;
justify-content: flex-start;
justify-content: flex-end;
Cross-axis alignment:
align-items: center;
Typical header:
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
Wrapping:
display: flex;
flex-wrap: wrap;
gap: 1rem;
Flexible content:
.content {
flex: 1;
}
Sidebar layout:
.layout {
display: flex;
gap: 2rem;
}
.sidebar {
flex: 0 0 250px;
}
.content {
flex: 1;
}
18. CSS Grid
Grid is designed for two-dimensional layouts.
.grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 2rem;
}
fr
grid-template-columns: 1fr 2fr;
The second column receives twice as much available space.
repeat()
grid-template-columns: repeat(3, 1fr);
minmax()
grid-template-columns:
repeat(
3,
minmax(200px, 1fr)
);
Responsive Grid
A very useful pattern:
.cards {
display: grid;
grid-template-columns:
repeat(
auto-fit,
minmax(250px, 1fr)
);
gap: 2rem;
}
This can adapt column count without a media query.
Grid Areas
.layout {
display: grid;
grid-template-columns:
250px 1fr;
grid-template-areas:
"sidebar content";
}
.sidebar {
grid-area: sidebar;
}
.content {
grid-area: content;
}
subgrid
.cards {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 3;
}
Flexbox or Grid?
Flexbox:
Arrange items in a row or column.
Grid:
Control rows and columns together.
19. Positioning
Important values:
position: static;
position: relative;
position: absolute;
position: fixed;
position: sticky;
Relative positioning is often used as the containing block for absolutely positioned children.
.card {
position: relative;
}
.badge {
position: absolute;
inset-block-start: 1rem;
inset-inline-end: 1rem;
}
Fixed:
.cookie-button {
position: fixed;
right: 2rem;
bottom: 2rem;
}
Sticky:
.sidebar {
position: sticky;
top: 1rem;
}
Do not solve stacking problems with absurd values such as z-index: 999999999. Understand stacking contexts.
Overflow:
overflow: hidden;
overflow: auto;
overflow: scroll;
Horizontal code scrolling:
.code {
overflow-x: auto;
}
Aspect ratio:
.thumbnail {
aspect-ratio: 16 / 9;
}
Object fit:
.thumbnail img {
width: 100%;
height: 100%;
object-fit: cover;
}
Typical page container:
.container {
width: min(100% - 2rem, 1200px);
margin-inline: auto;
}
20. Responsive design
A practical approach is mobile first.
Base layout:
.cards {
display: grid;
gap: 1rem;
}
Larger screens:
@media (width >= 700px) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
Wider still:
@media (width >= 1100px) {
.cards {
grid-template-columns: repeat(3, 1fr);
}
}
Modern range syntax:
@media (600px <= width < 1000px) {
}
Breakpoints should come from the layout, not from a list of device models.
Ask:
At what width does this layout stop working well?
21. Container Queries
A media query reacts to the viewport.
A container query reacts to a component container.
.card-wrapper {
container-type: inline-size;
}
@container (width >= 500px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
}
This lets the same component adapt differently in a sidebar and a main column.
Named container:
.products {
container: products / inline-size;
}
@container products (width >= 700px) {
...
}
22. Modern CSS
@layer
Explicitly control cascade layer order:
@layer reset, base, components, utilities;
@layer reset {
* {
box-sizing: border-box;
}
}
@layer base {
body {
font-family: system-ui;
}
}
@layer components {
.button {
padding: 0.7rem 1rem;
}
}
@layer utilities {
.hidden {
display: none;
}
}
CSS nesting
Instead of:
.card {
padding: 2rem;
}
.card h2 {
font-size: 2rem;
}
.card a:hover {
color: red;
}
you can write:
.card {
padding: 2rem;
h2 {
font-size: 2rem;
}
a {
color: blue;
&:hover {
color: red;
}
}
}
Media query inside a rule:
.card {
padding: 1rem;
@media (width >= 700px) {
padding: 2rem;
}
}
Simple class convention
.card
.card-title
.card-image
.card-description
.card-actions
State:
.card.is-active
Variant:
.card.card-featured
Design tokens
:root {
--color-bg: #fafafa;
--color-surface: #fff;
--color-text: #171717;
--color-muted: #666;
--color-primary: #4058d6;
--color-border: #ddd;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2rem;
--space-6: 3rem;
--radius-small: 0.3rem;
--radius-medium: 0.7rem;
--radius-large: 1.2rem;
}
23. Dark mode
:root {
color-scheme: light dark;
--bg: white;
--text: #181818;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111;
--text: #eee;
}
}
body {
background: var(--bg);
color: var(--text);
}
24. Animations
Transition
.button {
background: blue;
transition:
background 200ms ease,
transform 200ms ease;
}
.button:hover {
background: navy;
transform: translateY(-2px);
}
Prefer specific properties over:
transition: all 1s;
Transform
transform: translateX(10px);
transform: translateY(-5px);
transform: scale(1.05);
transform: rotate(5deg);
Combine transforms:
transform:
translateY(-2px)
scale(1.02);
@keyframes
@keyframes pulse {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.logo {
animation: pulse 2s infinite;
}
Reduced motion
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms;
animation-iteration-count: 1;
transition-duration: 0.01ms;
}
}
25. Styling forms
.form {
display: grid;
gap: 1.5rem;
}
.field {
display: grid;
gap: 0.4rem;
}
input,
textarea,
select {
width: 100%;
padding: 0.8rem 1rem;
font: inherit;
border: 1px solid #aaa;
border-radius: 0.4rem;
background: white;
color: #111;
}
Focus
Never remove focus without a meaningful replacement.
input:focus-visible,
textarea:focus-visible,
button:focus-visible,
a:focus-visible {
outline: 3px solid royalblue;
outline-offset: 3px;
}
Invalid state
input:invalid:not(:placeholder-shown) {
border-color: red;
}
26. CSS reset / base
A sensible starting point:
*,
*::before,
*::after {
box-sizing: border-box;
}
html {
hanging-punctuation: first last;
}
body {
margin: 0;
min-height: 100dvh;
font-family:
system-ui,
sans-serif;
line-height: 1.6;
}
img,
picture,
svg,
video {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
}
h1,
h2,
h3,
h4 {
line-height: 1.2;
}
p,
h1,
h2,
h3,
h4 {
overflow-wrap: break-word;
}
27. Project organisation
Small project:
project/
│
├── index.html
├── about.html
├── contact.html
│
├── css/
│ └── style.css
│
├── images/
│ ├── hero.webp
│ └── logo.svg
│
└── js/
└── main.js
Larger CSS structure:
css/
│
├── reset.css
├── tokens.css
├── base.css
├── layout.css
├── components.css
└── utilities.css
Do not split CSS into dozens of files without a real need.
28. Complete page example
HTML
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1"
>
<title>Null Yard</title>
<meta
name="description"
content="Experimental software projects."
>
<link
rel="stylesheet"
href="style.css"
>
</head>
<body>
<header class="site-header">
<div class="container header-inner">
<a class="logo" href="/">
Null Yard
</a>
<nav
class="navigation"
aria-label="Main navigation"
>
<a href="#projects">Projects</a>
<a href="#about">About</a>
<a href="#contact">Contact</a>
</nav>
</div>
</header>
<main>
<section class="hero">
<div class="container">
<p class="hero-label">
Software experiments
</p>
<h1>
I build things
that solve
real problems.
</h1>
<p class="hero-description">
Go, HTML, CSS, automation
and AI experiments.
</p>
<a class="button" href="#projects">
View projects
</a>
</div>
</section>
<section class="projects" id="projects">
<div class="container">
<header class="section-header">
<h2>Projects</h2>
<p>A few things I am working on.</p>
</header>
<div class="project-grid">
<article class="card">
<h3>Web Monitor</h3>
<p>Automatic monitoring of promotion pages.</p>
<a href="#">View project</a>
</article>
<article class="card">
<h3>Example Site</h3>
<p>A minimalist platform for publishing stories.</p>
<a href="#">View project</a>
</article>
<article class="card">
<h3>Hermes Tools</h3>
<p>Experiments with AI agents and automation.</p>
<a href="#">View project</a>
</article>
</div>
</div>
</section>
<section class="about" id="about">
<div class="container prose">
<h2>About Null Yard</h2>
<p>
Null Yard is a place for small
experimental software projects.
</p>
</div>
</section>
</main>
<footer class="site-footer" id="contact">
<div class="container">
<p>© 2026 Null Yard</p>
</div>
</footer>
</body>
</html>
CSS
@layer reset, base, layout, components;
@layer reset {
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
}
img,
svg,
picture {
display: block;
max-width: 100%;
}
button,
input,
textarea,
select {
font: inherit;
}
}
@layer base {
:root {
--color-bg: #f7f7f7;
--color-surface: #ffffff;
--color-text: #161616;
--color-muted: #666666;
--color-primary: #4058d6;
--color-border: #dddddd;
--space-small: 0.5rem;
--space-medium: 1rem;
--space-large: 2rem;
--space-xl: 4rem;
--radius: 0.75rem;
}
body {
min-height: 100dvh;
font-family:
system-ui,
sans-serif;
font-size: 1rem;
line-height: 1.6;
background: var(--color-bg);
color: var(--color-text);
}
h1,
h2,
h3 {
line-height: 1.15;
text-wrap: balance;
}
h1 {
font-size: clamp(2.5rem, 7vw, 6rem);
max-width: 15ch;
margin-block: 0 1.5rem;
}
h2 {
font-size: clamp(2rem, 4vw, 3rem);
}
a {
color: var(--color-primary);
text-underline-offset: 0.2em;
}
:focus-visible {
outline: 3px solid var(--color-primary);
outline-offset: 4px;
}
}
@layer layout {
.container {
width: min(100% - 2rem, 1200px);
margin-inline: auto;
}
.header-inner {
min-height: 70px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 2rem;
}
.project-grid {
display: grid;
grid-template-columns:
repeat(
auto-fit,
minmax(
min(250px, 100%),
1fr
)
);
gap: 1.5rem;
}
}
@layer components {
.site-header {
border-block-end: 1px solid var(--color-border);
background: var(--color-surface);
}
.logo {
color: inherit;
font-weight: 800;
text-decoration: none;
}
.navigation {
display: flex;
flex-wrap: wrap;
gap: 1.5rem;
a {
color: inherit;
text-decoration: none;
&:hover {
color: var(--color-primary);
}
}
}
.hero {
padding-block: clamp(5rem, 12vw, 10rem);
}
.hero-label {
color: var(--color-primary);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.1em;
}
.hero-description {
max-width: 50ch;
font-size: 1.2rem;
color: var(--color-muted);
margin-block-end: 2rem;
}
.button {
display: inline-block;
padding: 0.8em 1.4em;
border-radius: 0.5rem;
background: var(--color-primary);
color: white;
font-weight: 700;
text-decoration: none;
transition: transform 150ms ease;
&:hover {
transform: translateY(-2px);
}
}
.projects,
.about {
padding-block: var(--space-xl);
}
.section-header {
margin-block-end: 2rem;
}
.card {
padding: 1.5rem;
border: 1px solid var(--color-border);
border-radius: var(--radius);
background: var(--color-surface);
h3 {
margin-block-start: 0;
}
}
.prose {
max-width: 70ch;
}
.site-footer {
margin-block-start: 4rem;
padding-block: 2rem;
border-block-start: 1px solid var(--color-border);
color: var(--color-muted);
}
}
29. Good practices
Use semantic HTML
Prefer:
<nav>
<main>
<article>
<button>
instead of:
<div class="nav">
<div class="main">
<div class="article">
<div class="button">
Do not encode layout in HTML
Do not add repeated <br> elements just to create spacing. Use CSS margins or gaps.
Do not use tables for layout
Tables are for data. Use Grid or Flexbox for layout.
Do not absolutely position everything
Layouts based on hard-coded top/left pixel positions break easily.
Prefer normal flow, Flexbox and Grid.
Avoid unnecessary fixed heights
Do not force a card to a fixed height when content length can vary.
Do not design for one resolution
A page may be used on a phone, tablet, laptop, large monitor, half-screen window or at 200% zoom.
Do not overuse breakpoints
First consider:
auto-fit
minmax()
clamp()
flex-wrap
container queries
Do not remove focus indication
Avoid:
*:focus {
outline: none;
}
Do not build everything with JavaScript
Modern HTML and CSS already provide a lot.
HTML:
details
summary
dialog
required
pattern
loading="lazy"
CSS:
Grid
Flexbox
:has()
sticky
container queries
animations
30. Cheat sheet
HTML
<html>
<head>
<body>
<header>
<nav>
<main>
<section>
<article>
<aside>
<footer>
<h1> ... <h6>
<p>
<strong>
<em>
<a>
<ul>
<ol>
<li>
<img>
<picture>
<figure>
<figcaption>
<table>
<thead>
<tbody>
<tr>
<th>
<td>
<form>
<label>
<input>
<textarea>
<select>
<option>
<button>
<fieldset>
<legend>
<details>
<summary>
<div>
<span>
CSS
color
background
font-family
font-size
font-weight
line-height
width
max-width
min-width
height
min-height
margin
padding
border
border-radius
display
flex
grid
gap
position
overflow
aspect-ratio
object-fit
transform
transition
animation
@media
@container
@layer
var()
calc()
min()
max()
clamp()
:is()
:where()
:not()
:has()
:hover
:focus-visible
:nth-child()
::before
::after
31. What you should know
To say “I know HTML and CSS”, you do not need to memorise every property.
You should understand:
- HTML document structure,
- semantics,
- links and images,
- forms,
- accessibility basics,
- CSS selectors,
- the cascade,
- specificity,
- the box model,
- units,
- typography,
- normal flow,
- Flexbox,
- Grid,
- positioning,
- responsive design,
- media queries,
- custom properties,
- animations,
- container queries,
clamp(),:has(),- CSS nesting,
@layer.
The most important skill is not memorising syntax. It is recognising the type of problem.
Examples:
This is a two-dimensional layout - use Grid.
This is a single row or column - use Flexbox.
The component should react to its own width - use a container query.
Text size should scale smoothly with the screen - use
clamp().
Mental model for building a page
content
↓
semantic HTML
↓
normal flow
↓
Grid / Flexbox
↓
responsive values
min() / max() / clamp()
↓
media queries only where
they are actually needed
↓
container queries
for independent components
↓
CSS variables
for shared values
↓
@layer
for cascade control
↓
nesting
for component readability
↓
JavaScript only where
logic is required
HTML should still make sense with CSS disabled.
CSS should present a well-designed HTML structure rather than compensate for a poor one.
This is a strong foundation for building modern websites without Bootstrap, Tailwind or JavaScript frameworks.
Official references
- HTML Living Standard: https://html.spec.whatwg.org/
- CSS specifications: https://www.w3.org/Style/CSS/specs.en.html
- MDN HTML: https://developer.mozilla.org/docs/Web/HTML
- MDN CSS: https://developer.mozilla.org/docs/Web/CSS