PART 1: HTML
Chapter 1: What are HTML and CSS?
This chapter introduces you to the two languages that shape almost every page on the web. HTML provides the structure and meaning of a page, while CSS provides the design, layout and visual polish. When they work together, you get everything from simple personal pages to the most elegant and complex sites you can imagine. Understanding these two languages is like learning the alphabet and rhythm of the web. Once you know them, the rest of the web begins to make sense in a steady and satisfying way.
Both languages are designed to be readable even if you are completely new to coding. They use everyday words, clear tags and simple rules. This makes them ideal as a first step into web creation. You will write your first lines quickly, see results instantly and learn through gentle practice rather than wrestling with complicated tools. Throughout this book, you will gradually build a set of small examples that join together into a complete, modern personal site.
If you have been unsure about building a website from scratch, think of this chapter as opening the front door. You will explore the basic ideas at a calm pace, discover what each language does, and start developing the confidence to shape a page by hand. No special background is needed. You already know how to read and think clearly, and that is enough to begin creating pages that look clean, behave well on different devices and feel like your own corner of the web.
What this book will help you build
This book guides you through the process of creating a complete personal website piece by piece. You begin with simple pages that focus on clear structure, readable text and meaningful organisation. As you progress, these pages grow into a small but fully formed site with a homepage, an About Me section, a gallery, a projects page and a contact page. Each part is built using plain HTML first, then gradually shaped with CSS so the site becomes more polished and expressive.
The goal is not to create a huge or complicated project. Instead, you build a site that feels clean, modern and purposeful. You learn how to organise content sensibly, how to present it attractively and how to make sure it behaves well on screens of all sizes. By the time you reach the later chapters, the site becomes something you could genuinely publish online without feeling that it is a beginner’s experiment.
Throughout the book, you also learn how small improvements can transform a page. A simple list becomes a styled navigation bar; a plain photo collection becomes a flexible gallery; a basic form becomes a neat and clear contact section. These gradual enhancements show how HTML and CSS complement each other. You see the structure, then you see how thoughtful design choices bring that structure to life.
How the two parts of the book work together
The book is divided into two parts because HTML and CSS each play a different role, yet both rely on one another to create a complete website. Part 1 focuses entirely on HTML. You learn how pages are constructed, how content is organised and how meaning is expressed through clear tags. During this stage the pages you make will look simple, but they will have solid foundations. Think of it as building a clean and carefully arranged structure that is ready to be styled.
Part 2 introduces CSS, which is the language that shapes the appearance of the pages you built earlier. You begin by adding small touches such as colour, spacing and readable typography. Later you explore modern layout systems, learn how to create responsive designs and add gentle interactive effects. Because every example in Part 2 is based on work you have already completed in Part 1, you always see the connection between structure and style.
This steady progression means you never face a blank page. By the time you reach the visual design chapters, you already have pages that feel familiar. You simply start enhancing them. You will learn that good websites are not created through decoration alone; they emerge from the balance between strong HTML structure and thoughtful CSS styling. This approach mirrors how real sites are built and gives you the confidence to develop your own designs with clarity and purpose.
Tools and setup for writing clean code
You do not need complex software to write HTML or CSS. Both languages work beautifully in any editor that can save plain text files. What matters most is choosing a tool that feels comfortable and does not distract you from the gentle rhythm of writing and adjusting your code. A good editor becomes a quiet workspace where each tag and style can be seen clearly.
Many readers choose Visual Studio Code (code.visualstudio.com). It is free, easy to install and available on all major platforms. It provides clear syntax colouring, a tidy file explorer and optional extensions that help with formatting or previewing pages. Sublime Text (sublimetext.com) is another popular choice because it opens instantly and keeps the screen uncluttered. Brackets has a softer feel and was designed with HTML and CSS in mind, which makes it welcoming for beginners who enjoy a calmer environment.
If you prefer minimalism, you can use a simple text editor such as Notepad, TextEdit or gedit, generally already installed on your computer. These tools show that HTML and CSS are just text files that the browser interprets. There is no gatekeeping and no secret format. This simplicity is part of the joy of learning web creation. Once you pick an editor, you only need a browser. All modern browsers include developer tools that let you inspect elements, change styles temporarily and understand why a page behaves as it does. These tools become a natural part of your workflow as your pages grow.
Using AI as a helpful companion
Learning HTML and CSS can feel like learning a new craft, and AI works well as a friendly guide who sits beside you while you build. You can ask for suggestions about layouts, colours or structure, and you can even share your files when you want a careful check for mistakes. AI can explain why something looks uneven, why a layout shifts in a strange way or why a selector is not matching the element you expected. These explanations help you understand the language more deeply rather than simply copying answers.
You remain the creator, and AI becomes a source of steady support. It can offer ideas, demonstrate alternatives and highlight patterns that make your pages easier to maintain. It never replaces your judgment or style. Instead it gives you options and clarifies concepts that might otherwise slow you down. Many beginners find that having AI nearby makes experimentation feel less risky and more enjoyable because questions can be answered quickly and clearly.
As your confidence grows, AI continues to be useful. When you try new layout techniques or explore responsive design, you can use AI to sanity check your code and confirm that your understanding matches how browsers behave. Treat AI as a companion who helps you learn at your own pace and encourages exploration without fear. With this approach you will develop your own skills while enjoying the reassurance of thoughtful feedback.
A personal site that can grow into more
A personal website is often the easiest and most enjoyable way to learn HTML and CSS. It gives you a clear purpose from the start because every page you create holds something meaningful. You might begin with a simple homepage and a short introduction, then add a gallery of favourite photos, a list of hobbies, a few project notes or a contact page. Even in their simplest form, these pages feel like a small home you are building on the web, with each chapter in this book adding another room.
As you progress, you will notice that the skills you learn for a personal site naturally apply to much larger projects. Clean HTML structure becomes the basis of professional pages. Good typography choices make articles and documentation easier to read. Thoughtful layouts using modern CSS techniques can turn a small gallery into a polished portfolio. A simple contact form can grow into a business enquiry form. Nothing you learn is wasted because everything scales smoothly.
The examples in this book are deliberately chosen so that readers can adapt them for many different purposes. If you want a calm personal page, the patterns here will support it. If you later decide to showcase creative work, start a small business site or build something more ambitious, you will already know how to design pages that feel clear, responsive and welcoming. A personal site is only the beginning; it is also a gentle path toward anything you choose to build next.
Chapter 2: Understanding Modern HTML
Modern HTML is a living language that grows carefully and steadily. You do not wait for a new version number to write modern pages. Instead you learn the current standard, use features that are well supported, and layer in optional enhancements as needed. This chapter explains how HTML works today, how browsers see your pages through the DOM, and how to structure documents that are clear, semantic and pleasant to maintain.
HTML as a living standard
HTML is maintained as a living standard. That means the specification is updated continuously rather than frozen into large versioned lumps. New elements and attributes are added when they prove useful, old ideas are refined, and guidance becomes clearer over time. Writers benefit because they can follow stable patterns now, then adopt small improvements when they are ready.
When planning features, think in layers. Start with solid HTML that works everywhere, then add optional CSS and JavaScript. If a brand new feature is not yet supported in some browsers, provide a fallback. This practical mindset keeps your pages resilient in the real world.
The DOM (Document Object Model)
The DOM is the browser’s in-memory representation of your document. Every element becomes a node, every attribute becomes a property, and the hierarchy of your HTML turns into a tree that scripts and tools can read or modify. You write source HTML, the browser parses it into the DOM, then layout and rendering turn that model into pixels.
From HTML source to a node tree
Parsing turns tags into nodes and text into text nodes. Whitespace that matters to layout is kept, while other whitespace may be collapsed. Comments are kept as nodes but never rendered. The important part is the shape of the tree, because that is what CSS selectors and scripts act upon.
<ul id="plan"><li>Write</li><li>Edit</li><li>Publish</li></ul>
This snippet becomes a root ul node with three child li nodes. A rule like #plan li matches each list item because the selector walks the same tree structure.
DOM may not match your source exactly; the parser can fix some mistakes by inserting missing elements such as a hidden <tbody> in a table.
Elements, attributes, and document structure
An element is a named piece of structure such as <p> or <img>. Attributes refine an element with extra data such as src, alt, href, or type. Together they form meaningful content that tools can understand and users can navigate.
Nesting that reflects meaning
Elements should be nested to match intent. A link that wraps a product name and price communicates a single tap target. A <figure> that wraps an image and a <figcaption> binds them into one semantic unit. Aim for structure that tells a clear story to both people and machines.
<figure><img src="chart.png" alt="Sales chart"><figcaption>Quarterly sales, 2024…</figcaption></figure>
alt on images and aria-label on interactive controls; screen readers rely on them.
Void elements and optional tags
Some elements are void, which means they have no closing tag. Examples include <img>, <br>, and <meta>. Other tags are optional in certain contexts, such as a closing <p> before another block. For clarity in books and tutorials, write the explicit form unless you are showing how the parser recovers.
Block and inline elements
HTML distinguishes between block elements that form the main layout flow, and inline elements that sit within a line of text. This default behavior can be changed with CSS, but learning the defaults helps you predict how content will flow before you add styles.
Common defaults
| Category | Examples | Notes |
| Block | <div>, <p>, <ul>, <article> | Start on a new line, expand to available width |
| Inline | <span>, <a>, <strong>, <code> | Flow within text, do not force a line break |
Use block elements to group and structure sections, and inline elements to annotate text with meaning such as emphasis or code styling.
Head elements <head>, metadata, and linking resources
The <head> holds information about the document rather than the content itself. Typical entries include character encoding, title, viewport settings, icons, and links to stylesheets. Search engines and social networks also read metadata to build previews.
A practical <head> template
<head>
<meta charset="utf-8">
<title>Site title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/site.css">
<meta name="description" content="Short description">
<meta property="og:title" content="Site title">
<meta property="og:description" content="Short description">
</head>
The order above places encoding first, then the human readable <title>, then viewport for mobile, followed by styles and descriptions. Keep it short and clean.
<title> that leads with the page topic; search results often truncate long titles.
Favicon and basic site identity
A favicon helps users recognize your site in tabs, bookmarks, and history. Provide a modern square icon, then link it in the <head>. You can include multiple sizes for different devices, but one clear 32×32 or 48×48 PNG covers most needs. These can be easily created in any simple graphical editor or there are online favicon creators you can use.
<link rel="icon" href="/icons/favicon.png" sizes="32x32">
For richer identity on mobile, add an apple-touch-icon and a theme color so the browser UI can match your brand.
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
<meta name="theme-color" content="#224466">
Semantic page structure
Semantic elements describe the roles of sections in a page. A <header> introduces context, <nav> groups primary links, <main> holds the main content for the page, <article> wraps a standalone piece, <aside> contains related material, and <footer> ends the page or section. These hints help assistive technology, search engines, and your future self.
A small semantic skeleton
<header>
<h1>Site title</h1>
<nav aria-label="Primary">
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
</header>
<main id="content">
<article>
<h2>Post title</h2>
<p>Intro text…</p>
</article>
<aside aria-label="Related">
<p>Links and notes…</p>
</aside>
</main>
<footer>
<p>© 2025 Your Name</p>
</footer>
<main> per page, and ensure it has the primary content so skip-to-content links work well.
Creating a clean and minimal homepage
A minimal homepage loads fast, reads clearly, and sets a consistent baseline for the rest of the site. Start with a tidy structure, small CSS, and simple identity. Avoid heavy frameworks until you have a need that justifies them.
A modern, compact HTML skeleton
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Andy Mann</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Design and code, simply done">
<link rel="icon" href="/icons/favicon.png" sizes="32x32">
<style>
html, body {
margin : 0;
padding : 0;
}
h1 {
font-size : 1.8rem;
margin : 0 0 .5rem 0;
}
h2 {
font-size : 1 .4rem;
margin : 1.5rem 0 .5rem 0;
}
p {
margin : .8rem 0;
}
</style>
</head>
<body>
<header>
<h1>Andy Mann</h1>
<nav aria-label="Primary">
<a href="/" aria-current="page">Home</a>
<a href="/work">Work</a>
<a href="/contact">Contact</a>
</nav>
</header>
<main id="content">
<h2>Hello</h2>
<p>Welcome to my personal website</p>
</main>
<footer>
<p>© 2025</p>
</footer>
</body>
</html>
This starter shows a small yet complete page. It sets encoding and viewport, declares a title and description, links an icon, and includes a tiny embedded stylesheet for fast first render.
Chapter 3: Text, Headings, Lists, and Links
This chapter focuses on the core ingredients of written pages. You will shape paragraphs, choose clear headings, build lists that guide the eye, and create links that connect pages into a small yet purposeful site. These elements form the quiet backbone of HTML. Once you know them well, everything else becomes easier to reason about.
Paragraphs and headings with clear meaning
HTML gives you simple, expressive tools for structuring text. A <p> creates a paragraph that holds a single idea. Headings create a logical ladder from top level titles down to finer sections. Use <h1> for the main heading of the page and move downward from there. Avoid skipping levels because the sequence helps assistive technology understand your structure.
The following example shows a short block that uses these elements in a steady and readable way.
<h1>My Garden Notes</h1>
<h2>Spring</h2>
<p>The first shoots appear and the birds grow louder as the days warm.</p>
<h3>Seeds to plant</h3>
<p>Carrots, basil, lettuce and a few surprise herbs…</p>
… is an entity that will be replaced with an ellipsis (…) when displayed.
Ordered and unordered lists
Lists help you group related thoughts into tidy, scannable units. HTML gives you two main types. An unordered list created with <ul> uses markers such as discs or circles. An ordered list created with <ol> uses sequence markers such as numbers or letters. Each entry sits inside its own <li> element, which keeps the structure predictable for both readers and browsers.
Choosing the right list
Use an unordered list when order does not matter. Use an ordered list when steps must happen in a specific sequence. This choice shapes how readers understand the content at a glance.
<ul>
<li>Notebook</li>
<li>Pencil</li>
<li>Snacks</li>
</ul>
<ol>
<li>Open the box</li>
<li>Plug in the cable</li>
<li>Press the power button</li>
</ol>
The default styles vary slightly between browsers, but unordered lists usually show small filled discs and ordered lists usually show numbers. These defaults are readable and work well for most pages.
Understanding list markers
You can change list markers directly in HTML without any CSS. Ordered lists accept a type attribute that switches the marker style. This keeps things simple at this early stage, and it shows how browsers can adjust the numbering on their own.
The type attribute works like this:
type="1"gives standard decimal numberstype="a"gives lowercase letterstype="A"gives uppercase letterstype="i"gives lowercase Roman numeralstype="I"gives uppercase Roman numerals
Here are some short examples that show each form in action.
<ol type="1">
<li>One</li>
<li>Two</li>
</ol>
<ol type="a">
<li>Alpha</li>
<li>Beta</li>
</ol>
<ol type="I">
<li>First</li>
<li>Second</li>
</ol>
Unordered lists do not support type in modern HTML. Browsers provide a simple filled disc by default which is enough for now. Later chapters will show how CSS can change markers for both list types in more flexible ways.
Nested lists
You can place one list inside another to show sub-items. Browsers automatically indent nested lists and adjust their markers. This lets you show relationships without extra styling.
<ul>
<li>Tasks
<ul>
<li>Prepare notes</li>
<li>Review questions</li>
</ul>
</li>
<li>Plan rest</li>
</ul>
Nesting is powerful, although it is best used lightly. Too many layers can make a page feel tangled.
<li> only after the final </li> of any sub or sub-sub lists. This keeps the structure predictable. If a list starts to feel crowded, split the content into separate headings and paragraphs instead.
Links, anchors, and navigation basics
Links are the bridges that connect one page to another. You create a link with the <a> element and give it an href attribute that points to the destination. Readers expect links to be clear and specific, so choose link text that states what the user will see when they follow it.
Link examples
The following links demonstrate internal navigation and a path to another site.
<a href="/about">About me</a>
<a href="/projects">Projects</a>
<a href="https://example.com">Visit Example</a>
You can also create internal anchors inside a longer page by adding an id to an element, then linking to it with a hash.
<h2 id="details">Details</h2>
<a href="#details">Jump to details</a>
id. Headings are common because they mark clear sections, but you can attach an id to a paragraph, a list item, or even a small span of text. A link that points to #that-id will jump straight to it.
HTML entities and special characters
Some characters have special roles in HTML, so you write them using entities. For example you write & to show an ampersand and < to show a less than sign. Entities also let you use characters such as © or … in a safe and predictable way.
Common entities
The table below shows a few everyday examples.
&- Ampersand<- Less than>- Greater than©- Copyright sign…- Ellipsis
These controlled forms make your pages well behaved in every browser.
Controlling line and word breaks
Most of the time you allow paragraphs to wrap naturally. There are moments when you need a deliberate break. You can force a line break with <br>, create a thematic divider with <hr>, or allow an optional word break with <wbr>, which allows you to specify where abrowser is allowed to create a line break inside a long string if the content would overflow.
Practical break examples
<p>Line one<br>Line two</p>
<hr>
<p>A verylongwordthatmight<wbr>break cleanly</p>
Use these sparingly because too many manual breaks can interrupt the natural flow of text.
<br> tags. A healthy structure guides the reader without extra breaks.
By mastering these text based elements, you give your future pages a firm foundation. The next chapter will build on this stable base.
Chapter 4: Images, Figures, and Simple SVG
Images bring tone and texture to a page. They can explain an idea, support a paragraph, or simply give the page a friendlier voice. HTML offers a small set of steady tools for placing images with meaning, wrapping them in a <figure> when they need context, and using inline SVG for sharp icons that scale cleanly. This chapter introduces these ideas at a gentle pace.
Placing images with purpose
The <img> element inserts an image into the flow of the page. You give it a src that points to the file and an alt text that states the essential meaning for people who use screen readers. Images without helpful alt text become obstacles, so keep the description plain and direct.
This small example shows a simple placement.
<img src="sunset.jpg" alt="Orange sky at dusk">
The browser displays the image at its natural size unless you add a few optional attributes. You can set a width or height directly on the element, and the browser preserves the aspect ratio if you provide only one dimension. You can also add an optional border, although most pages leave borders to CSS.
<img src="sunset.jpg" alt="Orange sky at dusk" width="300" border="1">
width, height, and border attributes are helpful for early experiments, but modern pages usually style images with CSS for more control and better responsiveness.
The example above forces the displayed width to 300 pixels. The browser scales the height automatically to keep the image balanced. The border attribute is included here because it helps you see the frame of the image without introducing CSS too early in the book. A cleaner and more flexible method using CSS will be shown later.
Using figure and figcaption
Some images carry meaning that deserves a small explanation. You can group an image with its caption inside a <figure>. This creates a semantic bundle that tools can understand and that layouts handle more gracefully.
A figure with a caption
Here is a simple pattern you will use often.
<figure>
<img src="lake.jpg" alt="Still water with morning light">
<figcaption>A quiet view from the northern path</figcaption>
</figure>
The caption should add context rather than restating the alt text. The alt is for clarity, while the caption is for understanding.
<figcaption> short. Long captions can blur the line between meaning and commentary.
Responsive images
Different devices benefit from different image sizes. A wide monitor can show a large, detailed image, while a small phone needs something lighter. The <picture> element allows the browser to choose which file to load based on screen width or pixel density.
A responsive example
The pattern uses one or more <source> elements followed by a regular <img> as a fallback.
<picture>
<source srcset="mountain-large.jpg" media="(min-width: 800px)">
<source srcset="mountain-medium.jpg" media="(min-width: 500px)">
<img src="mountain-small.jpg" alt="Mountain ridge in sunlight">
</picture>
The browser checks the media conditions from top to bottom and picks the first match. If none match, it uses the <img> at the end. This keeps loading efficient without any scripts.
Inline SVG for icons and small diagrams
SVG images scale smoothly at any size, which makes them ideal for icons and simple diagrams. Inline SVG sits directly in your HTML so the browser can draw it without loading another file. The shapes stay crisp on all screens.
A tiny icon example
This example draws a small heart shape using SVG.
<svg width="32" height="32" viewBox="0 0 32 32" aria-label="Heart icon">
<path d="M16 29 L3 16 C-2 10 4 2 10 5 C13 7 16 11 16 11 C16 11 19 7 22 5 C28 2 34 10 29 16 Z"></path>
</svg>
It looks like this:
The path draws the shape with a series of coordinates. You do not need to memorise these commands at this stage. What matters is the idea that SVG is made from small, readable drawing steps that the browser traces into smooth shapes. Later on in Chapter 9 you will learn how to edit and simplify these paths, and how to build your own small icons with confidence. The important idea is that SVG is text based, lightweight, and designed for clean vector shapes.
Chapter 5: Audio, Video, and Embedding Media
This chapter introduces the core HTML features for sound, moving pictures and safe embedding. You will learn how to use the audio and video elements, add multiple sources, provide captions and transcripts, and embed trusted third-party players inside controlled iframe containers. By the end of the chapter you will have a simple home video page and a clear approach to safe external media.
Using the audio element
The audio element plays sound files directly in the page. The browser shows a native control when you include the controls attribute. Always offer at least two formats if possible so the browser can pick one it supports.
A minimal audio player
This example provides two common formats. The browser selects the first it can play and ignores the rest.
<audio controls>
<source src="media/song.ogg" type="audio/ogg">
<source src="media/song.mp3" type="audio/mpeg">
Your browser cannot play audio in this format.
</audio>
intro_theme.mp3. If you show a template placeholder, you can write media/{clip_name}….mp3 to indicate that part of the name is unknown.
Common audio attributes
These attributes control playback and loading behavior. Use them sparingly so the page remains friendly to the user and to assistive technology.
| Attribute | Meaning |
controls |
Shows built-in play and seek controls |
autoplay |
Requests immediate playback; most browsers require muted to be present |
loop |
Restarts at the end without user interaction |
muted |
Starts with sound off; often needed with autoplay |
preload |
Hints loading strategy: none, metadata, or auto |
Accessible labeling and transcripts
Give every media element a clear label and provide a text transcript near the player. Screen reader users can then understand the content and context.
<figure>
<figcaption>Podcast: Building a layout grid</figcaption>
<audio controls preload="metadata">
<source src="media/layout_grid.ogg" type="audio/ogg">
<source src="media/layout_grid.mp3" type="audio/mpeg">
</audio>
<p>Transcript: In this episode we discuss columns, gutters and rhythm …</p>
</figure>
Using the video element
The video element displays movies with native playback controls. You can provide several sources, a poster image, and one or more track elements for captions and subtitles.
A minimal video player
This player includes controls and a poster image that appears before playback starts.
<video controls width="640" height="360" poster="media/teaser.jpg">
<source src="media/teaser.webm" type="video/webm">
<source src="media/teaser.mp4" type="video/mp4">
Your browser cannot play this video.
</video>
Sizing and responsiveness
Fixed width and height set the intrinsic size. For flexible layouts, use CSS to make the player scale with the container while preserving aspect ratio.
<style>
.video-fluid { max-width: 100%; height: auto; }
</style>
<video class="video-fluid" controls poster="media/clip.jpg">
<source src="media/clip.mp4" type="video/mp4">
</video>
Captions with track
Provide captions with the track element. Set kind="captions", include a srclang code and a human-readable label. Use WebVTT files with the .vtt extension.
<video controls>
<source src="media/interview.mp4" type="video/mp4">
<track kind="captions" src="media/interview.en.vtt" srclang="en" label="English" default>
<track kind="subtitles" src="media/interview.es.vtt" srclang="es" label="Español">
</video>
A simple home video page
This small page presents a home movie with captions, fallback text and a brief description. You can adapt the structure to a gallery or to a series of lessons.
<!doctype html><html lang="en">
<head>
<meta charset="utf-8">
<title>Home Movie: Autumn Walk</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body { font-family: system-ui, sans-serif; line-height: 1.5; margin: 2rem; }
.video { max-width: 720px; }
.video video { width: 100%; height: auto; }
.meta { color: #555; font-size: 0.9rem; }
</style>
</head>
<body>
<h1>Autumn Walk</h1>
<p class="meta">Recorded in the local park on a windy afternoon; gentle ambient sound.</p>
<section class="video">
<h2>Watch the video</h2>
<p>Use the built-in controls to play, pause or seek. Captions describe spoken words and important sounds.</p>
<video controls poster="media/autumn_walk.jpg">
<source src="media/autumn_walk.webm" type="video/webm">
<source src="media/autumn_walk.mp4" type="video/mp4">
<track kind="captions" src="media/autumn_walk.en.vtt" srclang="en" label="English" default>
Your browser cannot play this video. You can <a href="media/autumn_walk.mp4">download the file</a> instead.
</video>
</section>
<section>
<h2>About this clip</h2>
<p>Filmed at 24 frames per second with a phone camera. Edited for color balance and trimmed for length. No filters were applied.</p>
</section>
</body>
</html>
Useful attributes and text tracks
Audio and video elements share many behaviors. The table below lists options you will use frequently, followed by a compact WebVTT sample that you can adapt for captions and subtitles.
| Element | Attribute | Purpose |
audio, video |
controls |
Show native UI for playback |
audio, video |
preload="none|metadata|auto" |
Hint whether to fetch data before play |
audio, video |
autoplay with muted |
Request silent auto start when appropriate |
video |
poster |
Placeholder image before playback |
video |
playsinline |
Suggest inline playback on mobile |
track |
kind="captions| |
Defines the role of the text track |
WebVTT files are plain text. Each cue has a time range and an optional cue identifier. Keep lines short for readability.
WEBVTT
00:00:00.000 --> 00:00:02.500
Footsteps on the gravel path
00:00:02.500 --> 00:00:05.000
Birdsong grows louder near the trees
Embedding trusted external media
Sometimes you will not host the files yourself. Popular services provide embeddable players that you place on the page. Use the provider’s recommended snippet and apply conservative permissions so the embed behaves safely.
YouTube privacy-enhanced embed
This variant reduces tracking by using the youtube-nocookie.com domain. The allow attribute grants specific features the player needs.
<iframe
width="560"
height="315"
src="https://www.youtube-nocookie.com/embed/VIDEO_ID?rel=0"
title="Video title"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen>
</iframe>
Vimeo embed with limited referrer
Set a strict referrer policy and avoid unnecessary query options. Replace VIDEO_ID with your identifier, for example 123456789.
<iframe
src="https://player.vimeo.com/video/VIDEO_ID"
width="560"
height="315"
title="Demo reel"
frameborder="0"
referrerpolicy="strict-origin-when-cross-origin"
allow="autoplay; fullscreen; picture-in-picture">
</iframe>
iframes and sandboxing
An iframe creates a nested browsing context that can isolate untrusted content. The sandbox attribute removes powerful capabilities by default, then you add back only what is required.
Basic iframe with sandbox
This frame can render simple markup from srcdoc. It cannot run scripts, submit forms or change the top page because the sandbox is active with no tokens.
<iframe
title="Isolated note"
sandbox
srcdoc="<p>Hello from an isolated frame.</p>">
</iframe>
Granting minimal permissions
Add tokens to allow only what the content needs. The table gives a few practical choices that you will use often. Keep the set as small as possible.
| Token | Effect |
allow-scripts |
Permit JavaScript execution inside the frame |
allow-same-origin |
Treat the frame as same origin; required for many embeds |
allow-forms |
Permit form submission inside the frame |
allow-popups |
Permit the frame to open new windows |
allow-presentation |
Permit presentation sessions such as casting |
<iframe
title="Trusted widget"
src="https://widgets.example.com/player?item={id}&opt=…"
sandbox="allow-scripts allow-same-origin"
referrerpolicy="strict-origin-when-cross-origin"
allow="fullscreen; picture-in-picture">
</iframe>
sandbox with Content-Security-Policy at the server to control what the frame can load. A careful combination limits risk even when the source changes over time.
Chapter 6: Tables for Real Data
For many years tables ruled the web. Early site builders treated them as invisible scaffolding that held every banner, column and button in place. A page might hide dozens of nested grids, each one nudging content into rigid arrangements. This worked in a practical sense but it was never the purpose of tables. They were built for data rather than layout. Once CSS became widely supported, the web slowly stepped away from these table labyrinths and returned the element to its original role. This chapter focuses on that role and shows how to build clean, meaningful data tables that help readers understand information at a glance.
Used well, a table becomes a careful map of values. Every row forms a small story, and every column tracks one attribute of that story. Presenting information in this structured way lets readers compare numbers, find patterns and move through the content with confidence. The sections that follow help you decide when a table is right, how to construct its main parts and how to shape a simple design even without CSS. By the end you will have a practical pattern that you can reuse whenever your projects call for real data.
When tables are the right choice
Use tables only when your content is truly tabular. If readers need to compare values across rows or follow patterns down a column, a table supports this naturally. If the information reads more like a list of features or a narrative, avoid forcing it into a grid because that can make the page harder to understand.
Recognising true tabular data
Think about the data as a set of relationships. If each row describes a single item and each column describes one attribute of that item, then you have a natural fit for a table. A price list, a timetable or a set of measurements works well. A paragraph of mixed facts does not.
Avoiding layout tables
Early web pages often used tables to lay out an entire page. This pattern is no longer appropriate because it makes the structure unclear for screen readers, search engines and anyone who relies on assistive tools. Modern HTML provides better layout options, so use tables only for genuine data.
Caption, thead, tbody, and tfoot
Data tables become easier to understand when you separate their parts. The caption explains the purpose of the table to machines such as screen readers. The thead groups the column labels, the tbody holds the main data and the tfoot holds summary values that help readers interpret the numbers. These elements do not change the visual appearance on their own. Their main job is to express meaning so assistive tools and automated processes can interpret the table correctly.
By contrast, tags such as the <th> element affect how the table looks to humans. Browsers treat header cells differently by centering the text and applying a bold style. This can feel too strong in a compact table or in layouts where you want precise control over alignment. If the built-in styling is not suitable, you can skip <th> entirely and place your header labels inside ordinary <td> cells. This keeps the visual weight even across the grid and leaves you free to highlight information in your own way.
Adding a caption
A caption appears at the top of the table and acts like a concise title. It should be short and informative because some assistive technologies treat the caption as the primary description.
<table>
<caption>Quarterly sales by region</caption>
<tr>
<td><b>Region</b></td>
<td><b>Q1</b></td>
<td><b>Q2</b></td>
<td><b>Q3</b></td>
</tr>
…
</table>
Structuring headers and body
Use thead to group column labels and tbody to wrap the main rows. This structure helps tools and browsers interpret the data. It also improves clarity when tables grow larger.
<table>
<caption>Daily temperatures</caption>
<thead>
<tr>
<td><b>Day</b></td>
<td><b>High</b></td>
<td><b>Low</b></td>
</tr>
</thead>
<tbody>
<tr>
<td>Monday</td>
<td>18°C</td>
<td>11°C</td>
</tr>
</tbody>
</table>
thead and tbody. Keep labels together to avoid confusing assistive technologies.
Using tfoot for summaries
The tfoot group holds totals or remarks that summarise your data. Browsers can place the foot at the end or the beginning depending on printing preferences, so keep the summary short and clear.
<table>
<caption>Weekly expenses</caption>
<thead>
<tr>
<td><b>Item</b></td>
<td><b>Cost</b></td>
</tr>
</thead>
<tbody>
<tr><td>Transport</td><td>£28</td></tr>
<tr><td>Food</td><td>£42</td></tr>
</tbody>
<tfoot>
<tr><td>Total</td><td>£70</td></tr>
</tfoot>
</table>
Styling tables without CSS
You can make tables legible with pure HTML features even when you avoid CSS. Attributes such as colspan, rowspan and scope help you express structure clearly. The border attribute still works in HTML although it is not recommended for long term documents, but it can help you explain concepts during learning.
Using colspan and rowspan
Merge cells when one value logically covers multiple columns or rows. This avoids repetition and reduces visual noise.
<table border="1">
<caption>Merged cells</caption>
<tr>
<td colspan="2"><b>Group A</b></td>
<td><b>Score</b></td>
</tr>
<tr>
<td rowspan="2">Team 1</td>
<td>Round 1</td>
<td>8</td>
</tr>
<tr>
<td>Round 2</td>
<td>7</td>
</tr>
</table>
Adding basic styling
The border attribute creates simple lines that help users track rows and columns. Although modern documents use CSS instead, this attribute is enough for quick prototypes or learning exercises.
<table border="1">
<caption>Simple grid</caption>
<tr><td>A</td><td>B</td><td>C</td></tr>
<tr><td>D</td><td>E</td><td>F</td></tr>
</table>
Plain HTML offers only limited ways to add colour to a table. You can use the bgcolor attribute on cells or rows, although it is an old feature that survives mostly for compatibility. It does work in simple learning examples, for instance to highlight a header row or to distinguish alternating rows, but it is not recommended for modern documents. When you want consistent colours or themes, you will eventually need CSS because that gives you clearer control and avoids mixing presentation with structure.
Building a small data table
Here you create a compact table that uses caption, thead, tbody and simple borders. This pattern is suitable for short statistical summaries or quick comparisons.
Putting the parts together
The example below lists the height of three popular peaks. It uses borders for clarity and a short caption for context.
<table border="1">
<caption>Mountain heights</caption>
<thead>
<tr>
<td><b>Mountain</b></td>
<td><b>Height (m)</b></td>
</tr>
</thead>
<tbody>
<tr><td>Ben Nevis</td><td>1345</td></tr>
<tr><td>Snowdon</td><td>1085</td></tr>
<tr><td>Scafell Pike</td><td>978</td></tr>
</tbody>
</table>
This is the core pattern for real data tables. As your projects grow, you can extend it with CSS for spacing, stripes and responsive layouts, but the essential structure will remain the same.
Chapter 7: Forms and User Interaction
Forms let people send information to your page. You can use them for small interactions that need no JavaScript or server. This chapter shows the essential elements and the built-in power of modern HTML forms. You will also learn small techniques that work locally such as mailto: and tel: links. These tools help you create useful pages that feel responsive and respectful to the reader.
Inputs, labels, and structure
Every form begins with a <form> element. Inside that element you place controls such as <input>, <label>, <textarea>, and <button>. Good structure makes a form clear and accessible. The most important rule is to associate each control with a label. You do this with a matching for and id.
<form action="" method="get">
<p>
<label for="name">Your name</label>
<input id="name" name="name" type="text">
</p>
<p>
<label for="email">Email address</label>
<input id="email" name="email" type="email">
</p>
<p>
<button type="submit">Send</button>
</p>
</form>
<label>. This automatically links them so you do not need for and id.
Group related controls with a <fieldset> and describe the group with a <legend>. This helps screen readers and makes long forms easier to read.
<form action="">
<fieldset>
<legend>Contact details</legend>
<label>Name <input name="name"></label>
<label>Email <input name="email" type="email"></label>
</fieldset>
</form>
Semantic order and keyboard flow
Place labels and inputs in a natural reading order. People often move through a form with the keyboard. The tab order follows the source order, so neat HTML becomes neat navigation.
Required attributes for clarity
Name each control with name. Without a name a control does not submit any value. Use unique id values to match labels and for CSS hooks where needed.
Textarea, buttons, autocomplete, and focus
Use <textarea> for longer passages of text that would feel cramped inside a single line. Browsers grow the control to show several lines at once so readers can see their thoughts gather in one place. A <textarea> can also hold placeholder hints, default values, and accessible labels. Treat it as a small writing space rather than a stretched input box and keep its purpose clear through a short label that sits close by.
Use <button> for actions such as submit, reset, or any custom task you define inside a form. A button carries meaning that a plain input does not and it adapts well to different devices. Labels inside a button are flexible so you can include short phrases, icons, or clear verbs such as Save or Search. This helps readers understand what will happen when they press it and gives you freedom to keep the form tidy.
Add the autocomplete attribute to help the browser suggest known values for names, addresses, phone numbers, and many other fields. These suggestions make forms friendlier and faster to complete, especially on mobile. Style focus states with care so that users can always see where the cursor is. Bright outlines or gentle highlights guide the eye during keyboard navigation and give the form a confident sense of place.
<form action="" method="get">
<p>
<label for="msg">Message</label>
<textarea id="msg" name="message" rows="5" cols="30" placeholder="Type your message…"></textarea>
</p>
<p>
<label for="city">City</label>
<input id="city" name="city" autocomplete="address-level2">
</p>
<p>
<button type="submit">Send message</button>
<button type="reset">Clear</button>
</p>
</form>
<style>/* minimal helpful focus styles */
:where(input, textarea, select, button):focus {
outline: 2px solid currentColor;
outline-offset: 2px;
}
</style>
Autofocus and helpful hints
Add autofocus to place the cursor in the first control. Add placeholder for short examples. Keep placeholders short since they are not a replacement for labels.
Useful input types
HTML provides many input types that validate and present friendly controls by default. These reduce errors and work without scripts.
<form action="" method="get">
<p>
<label for="e">Email</label>
<input id="e" name="email" type="email" required>
</p>
<p>
<label for="u">Website</label>
<input id="u" name="url" type="url" placeholder="https://…">
</p>
<p>
<label for="d">Date</label>
<input id="d" name="date" type="date">
</p>
<p>
<label for="r">Satisfaction</label>
<input id="r" name="satisfaction" type="range" min="0" max="10" step="1">
</p>
<p>
<label for="c">Favorite color</label>
<input id="c" name="color" type="color" value="#3366cc">
</p>
</form>
Numbers and phones
For digits use type="number" with min, max, and step. For phone numbers keep type="tel". This does not validate the format, however it opens a phone keypad on mobile which makes entry easier.
Validation and helpful attributes
Modern browsers validate many inputs automatically. You can guide this with attributes. This happens before a submit leaves the page, so it works with or without a server.
<form action="" method="get">
<p>
<label for="em">Email (required)</label>
<input id="em" name="email" type="email" required>
</p>
<p>
<label for="pw">Password (8 characters minimum)</label>
<input id="pw" name="password" type="password" minlength="8" required>
</p>
<p>
<label for="age">Age (18 to 120)</label>
<input id="age" name="age" type="number" min="18" max="120" step="1">
</p>
<p>
<label for="zip">Postal code (digits only)</label>
<input id="zip" name="postal" inputmode="numeric" pattern="\d{4,10}" placeholder="12345">
</p>
<p>
<button type="submit">Check</button>
</p>
</form>
title on a control to provide a friendly pattern hint. Browsers show this in their validation message.
Choosing get or post
When a form is submitted the browser has to package the values and send them somewhere. The method attribute tells the browser which format to use. In a typical site this information travels to a server that reads the data and replies with a new page. Even if you are not using a backend it is still useful to understand what these methods mean because they shape how the browser prepares the submission.
method="get" creates a URL that contains all the form values as key and value pairs. These appear in the address bar after a question mark, so the final link becomes a shareable request that anyone can paste or bookmark. This makes get suitable for searches, filters, and any small piece of public information. The format is a standard URL query so anything sensitive should not be sent this way.
method="post" hides the values inside the request body instead of appending them to the URL. The browser still sends the data to the destination but it does not appear in the address bar. This is better for longer messages, uploaded files, or private details. The submission is not a URL format so it cannot be bookmarked or revisited. When you work locally without a server the data does not go anywhere, however you still choose a method so the browser knows which structure to use when it prepares the action.
Creating a simple contact form
You can build a helpful contact page that works locally. The approach depends on your goal. The next examples show three patterns that do not need JavaScript or a custom server.
mailto: submit
A mailto: action asks the browser to open the reader’s email app with the form values. The values appear as message content. This is simple and can be enough for small personal sites.
<form action="mailto:your.name@example.com" method="post" enctype="text/plain">
<p>
<label for="mname">Your name</label>
<input id="mname" name="Name" required>
</p>
<p>
<label for="memail">Your email</label>
<input id="memail" name="Email" type="email" required>
</p>
<p>
<label for="mbody">Message</label>
<textarea id="mbody" name="Message" rows="6"></textarea>
</p>
<p>
<button type="submit">Email this to me</button>
</p>
</form>
mailto: depends on the user having a local email app. On some devices nothing happens, or values may be formatted differently. Treat this as a convenience rather than a guaranteed workflow.
tel: and phone-friendly links
On mobile a tel: link opens the dialer. You can place these links in or near a form so a reader can act at once without submitting anything.
<p>Prefer to call? <a href="tel:+441234567890">+44 1234 567 890</a></p>
You can also guide messaging apps. The sms: scheme opens the texting app with a number, and some platforms support a body parameter. Use this for quick replies or confirmations.
<p>Send a quick SMS: <a href="sms:+441234567890">+44 1234 567 890</a></p>
Download as a file
Sometimes the reader wants to keep what they typed. You can place the values into a data URL and offer a download link that contains the text. This needs no script when the content is fixed. When the content is dynamic you would normally use JavaScript. The next example shows a static pattern with a prepared file link.
<p><a download="contact.txt" href="data:text/plain,Name%3A%20…%0AEmail%3A%20…%0AMessage%3A%20…">Download example</a></p>
Search forms that target other pages
Another useful pattern is a form that redirects to an existing site and passes a search term. This needs no backend. You build a query string that matches the target site’s public search format.
<form action="https://www.example.com/search" method="get">
<p>
<label for="q">Search Example</label>
<input id="q" name="q" placeholder="Keywords">
<button type="submit">Search</button>
</p>
</form>
Datalist for friendly suggestions
<datalist> pairs with an <input> to offer short suggestions while the user types. The browser shows these suggestions in a small dropdown that feels similar to an autocomplete list, yet everything remains inside the page with no scripts involved. You control the list by placing <option> elements inside the datalist, each one representing a hint or a commonly chosen value.
This feature is helpful when you want to guide the user without forcing a fixed choice. The reader can pick a suggestion or ignore it and type something entirely new. That balance makes <datalist> ideal for topics, categories, tags, or any field where a gentle nudge is better than a strict menu. It also keeps the form light because the browser handles all the interaction on its own.
Since the list is local and stored in the HTML, it loads instantly and works the same online or offline. You can expand or trim the options whenever you like because each suggestion is a simple element. This gives your forms a friendly, guided feel while keeping the code clean and approachable.
<label for="topic">Topic</label>
<input id="topic" name="topic" list="topics">
<datalist id="topics">
<option value="Support">
<option value="Billing">
<option value="Feedback">
<option value="Other">
</datalist>
A complete no-backend contact block
This final example combines a simple form with mail, phone, and a direct email link. It gives readers several ways to reach you without scripts.
<section id="contact">
<h3>Contact me</h3>
<p>Use the form or choose a quick option below.</p>
<form action="mailto:your.name@example.com" method="post" enctype="text/plain">
<p>
<label for="cn">Your name</label>
<input id="cn" name="Name" required autocomplete="name">
</p>
<p>
<label for="ce">Your email</label>
<input id="ce" name="Email" type="email" required autocomplete="email">
</p>
<p>
<label for="ct">Topic</label>
<input id="ct" name="Topic" list="ctopics" placeholder="Choose or type…">
<datalist id="ctopics">
<option value="Support">
<option value="Quote">
<option value="Feedback">
</datalist>
</p>
<p>
<label for="cm">Message</label>
<textarea id="cm" name="Message" rows="6" placeholder="How can I help?"></textarea>
</p>
<p>
<button type="submit">Send by email app</button>
</p>
</form>
<p>Quick options: <a href="mailto:your.name@example.com">Email link</a> · <a href="tel:+441234567890">Call</a> · <a href="sms:+441234567890">Text</a></p>
</section>
You now have practical tools that work anywhere. With careful labels, sensible types, and built-in validation you can build forms that respect the reader. Local patterns such as mailto: and tel: add simple paths for action while you keep your pages light and fast.
Chapter 8: Accessibility in Everyday HTML
Accessibility begins with the gentle craft of writing HTML that makes sense to both people and machines. A reader should be able to move through your page with ease whether they use a mouse, a keyboard, a screen reader, or a tool that digests your content on their behalf. This chapter shows how small choices such as clear alt text, meaningful landmarks, and friendly metadata can open your pages to everyone. These ideas live in the everyday structure of HTML rather than in advanced code, which makes them ideal habits to build early.
Every tag carries a tiny hint of intention. Headings form a natural outline, paragraphs carry ideas in sequence, and links point toward action. When these pieces line up well, assistive tools can paint a clear mental picture of your page. Good accessibility is not a bolt-on feature; it grows from simple, steady markup that explains itself. As you read through the following sections you will see how each technique adds another layer of clarity without changing the visible design.
This chapter also touches on helpful metadata such as lang, title, ARIA attributes, and structured descriptions. These elements guide screen readers, crawlers, and other automated tools by whispering meaning in places humans may never see. Taken together they form a quiet support system that lets your content travel smoothly across devices, languages, and abilities.
Alt text that helps everyone
Alt text tells a reader what an image represents. A screen reader speaks it aloud; search engines use it to understand the topic; and when a connection fails the browser shows it in place of the missing picture. A short phrase is usually enough. Describe the purpose of the image rather than the pixels. If the image is purely decorative you can use an empty alt attribute so assistive tools skip it. This keeps the flow clean and avoids cluttering the experience with details that serve no real meaning.
A helpful alt description captures intent. A chart might be described by its trend rather than its colors; a product image might highlight what makes the item useful; a portrait might name the person and their role. Each piece of alt text becomes a small companion for browsers that cannot display the image and for readers who cannot see it. When used with care it turns visuals into shared understanding.
alt="". This tells assistive tools that the element adds no information.
When alt text meets metadata
Alt text often works alongside attributes such as title or ARIA descriptions. These extra hints help when an image acts like a control or when more context is needed. Screen readers combine these clues to build a full understanding, so keep the message clear and consistent across each attribute.
Semantic roles and landmarks
HTML provides many elements that describe the shape of a page. Tags such as <header>, <nav>, <main>, <aside>, and <footer> act as landmarks. Screen readers use them as navigation anchors, letting the reader jump to the main content, browse the navigation, or explore side information. These landmarks also help crawlers understand how your page is organised.
Most built-in HTML elements carry natural roles. A list is a list, a button is a button, and a heading is a point in the outline. When you use these elements as intended you give assistive technology a set of trusted patterns to follow. If you ever create a custom widget or repurpose an element you can use ARIA roles to describe its purpose. These roles behave like small explanatory notes for tools that cannot see the visual styling.
Metadata that guides structure
Some metadata lives above the page content. Attributes such as lang tell a screen reader which voice and pronunciation to use. <meta> tags can describe authorship, topics, summaries, and character encoding. Crawlers and AI tools use this information to classify the page, build previews, or understand relationships. These pieces do not alter the layout yet they give your content a stronger identity.
Keyboard friendly thinking
A page that works well with a keyboard shows a clear focus outline, respects logical tab order, and responds reliably to common keys. Many people navigate this way because it is quick, comfortable, or necessary. The browser follows the source order of your HTML, so a well-structured document becomes a smooth keyboard journey. Each link, button, or form control should be reachable and obvious when highlighted.
Focus style is one of the simplest improvements you can add. A visible outline makes the active element easy to spot and gives confidence during navigation. Avoid disabling outlines because they are essential for non-mouse users. Instead you can restyle them so they fit the look of your page while staying bold enough to see. Even a small color highlight or a thicker border can make a meaningful difference.
Custom controls need a little extra care. A script-driven button should still behave like a button with keyboard activation. A carousel should respond to arrow keys. A hidden menu should open and close in predictable ways. ARIA attributes such as aria-expanded, aria-controls, and aria-label give these elements the clarity that built-in elements already enjoy. These small touches make your page feel steady and welcoming for every kind of reader.
Chapter 9: Using the Canvas and SVG
Modern pages can hold more than text and layout. They can also draw pictures, charts, icons, and animated scenes directly in the browser. HTML offers two main tools for this: the <canvas> element and SVG. Each tool approaches drawing in its own way. Canvas behaves like a blank painting surface that JavaScript controls pixel by pixel. SVG behaves like a collection of shapes that the browser treats as real elements. Learning both gives you a gentle introduction to visual creativity on the web.
This chapter explores what canvas is for, how to draw a simple shape with JavaScript, how SVG works, and where to continue your learning. Each example is small and friendly because these features can grow complex quickly. Once you know how they think, you can decide which tool fits your ideas.
What canvas is used for
The <canvas> element provides a drawing surface that lives inside the page. You control every mark on that surface with JavaScript. When you draw a shape or a line the pixels change immediately, much like painting on a real canvas. This approach is ideal for fast, dynamic graphics such as games, data visualisation, particle effects, and interactive tools. Because canvas works at the pixel level it refreshes quickly and does not need to treat each shape as a separate element.
Canvas is not part of the document structure. A screen reader cannot describe the contents because the drawing lives only inside pixels. For this reason authors often pair canvas with accessible text that sits nearby. This keeps the page friendly while still allowing for vivid graphics. Think of canvas as a live sketchbook whose drawings exist only in the moment they appear.
One helpful note is that canvas begins empty. If you resize it the contents vanish because the browser resets the pixel surface. This can feel surprising at first but it is part of the canvas model. Every frame is a fresh painting space, which is why animation and repeated drawing work so well here.
A small rectangle drawn with JavaScript
To draw on a canvas you place the element in your HTML then use JavaScript to select it and paint onto its context. The context is the toolset that handles strokes, fills, shapes, and pixel operations. The next example shows the smallest possible setup. It creates a canvas, gets the 2D context, and draws a blue rectangle.
<canvas id="box" width="200" height="120"></canvas>
<script>
const canvas = document.getElementById('box');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'steelblue';
ctx.fillRect(20, 20, 160, 80);
</script>
The browser treats the canvas as a single object. The rectangle is not a shape in the HTML tree; it is a patch of coloured pixels. If you want to move it or animate it you simply clear the area and draw again. This makes the canvas a powerful tool for visual experiments where speed matters more than structural meaning.
Why canvas and SVG feel different
Canvas behaves like a bitmap where every update rewrites pixels. SVG behaves like a collection of objects that the browser understands as shapes. You will feel this difference the moment you interact with them. Canvas reacts quickly to motion and animation; SVG offers clean shapes that scale without losing sharpness. These strengths help you choose the right tool for each task.
Drawing shapes with SVG
SVG stands for Scalable Vector Graphics. An SVG drawing is made from shapes such as lines, circles, paths, and polygons. Because these shapes live in the document tree they behave like real HTML elements. You can style them with CSS, animate them, or attach events. Screen readers can also understand labelled shapes because SVG supports titles, descriptions, and metadata.
SVG is ideal for icons, logos, charts, diagrams, and any graphic that must stay crisp at all sizes. The browser renders the image from mathematical instructions rather than pixels so the result stays sharp on every screen. The next example draws a simple green circle.
<svg width="120" height="120" viewBox="0 0 120 120">
<circle cx="60" cy="60" r="40" fill="seagreen"></circle>
</svg>
This circle is a true element. You can style it with CSS, animate it with <animate>, or wrap it with accessibility descriptions. SVG also supports complex paths that can form icons, characters, or gentle curves. Because the drawing instructions are text you can edit them by hand or generate them with tools.
Combining SVG and HTML
SVG has its own namespace yet it fits smoothly into a page. You can wrap it in a figure, place it in a list, or style it with your normal CSS rules. Complex scenes remain accessible because each shape is visible to assistive tools. Many designers mix SVG icons with text labels so the meaning stays clear at every size.
Where to learn more
Canvas and SVG both reward small steps. Once you have drawn a few shapes you can explore richer topics. The canvas API offers paths, gradients, patterns, pixel manipulation, and animation loops. SVG offers transforms, clipping paths, masks, and built-in animation elements. Both tools can collaborate with CSS and JavaScript so you can grow your experiments slowly.
The official MDN documentation provides clear examples for each feature. Online playgrounds let you test drawings and share them easily. You will also find many open source libraries such as D3, Paper.js, and charting tools that build on canvas or SVG. These libraries can teach you the patterns behind complex illustrations and show how modern sites weave graphics into the everyday web.
By learning both approaches you gain a flexible creative toolkit. Canvas gives you raw control over pixels; SVG gives you precise shapes that scale with grace. Together they invite you to draw ideas directly into your pages.
PART 2: CSS
Chapter 10: CSS Basics and the Cascade
CSS arrived because the early web felt like a stack of cardboard signs held together with tape. Designers kept trying to decorate entire pages using only HTML, which meant inventing odd tricks with tables, spacer images and stray attributes. Pages worked, yet every attempt to adjust the look forced authors to rewrite content and presentation together as if they were welded. CSS split that weld. It let authors write clean HTML for meaning and then layer visual rules on top so structure stayed steady while style could float freely above it. This separation also raised an interesting question about what should happen when several rules try to style the same element… and that puzzle eventually shaped the cascade.
Inline styles vs stylesheets
Before stylesheets existed, the easiest way to influence an element’s look was to place a style attribute right on the tag. Inline rules work, yet they cling to the element so tightly that the HTML becomes tangled with visual details. A stylesheet, by contrast, lives in its own space and sends its rules outward like a gentle drizzle of paint. This keeps HTML calm and readable while letting you change the look of an entire site by editing one file.
<p style="color: blue">Inline styles feel glued to the element.</p>
<link rel="stylesheet" href="styles.css">
How CSS targets elements
Every CSS rule begins with a selector. A selector acts like a small spotlight that tells the browser which elements should receive the declarations inside the braces. These can be simple spotlights such as p or .tagline, or they can combine in layered ways such as nav ul li a. The browser reads the selector, finds all matching elements, then applies the declarations in order.
p {
color: green;
}
.tagline {
font-size: 1.25rem;
}
nav ul li a {
text-decoration: none;
}
Specificity and inheritance
When multiple rules try to paint the same element, the browser settles the dispute using specificity. Each selector carries a weight based on its parts. An ID is heavier than a class, and a class is heavier than a plain element name. The browser compares these weights and chooses the strongest rule. Alongside this, inheritance lets some properties flow gently down from parent to child. Text color and font size often inherit, yet layout properties such as margin and padding do not.
#main-title {
color: red;
}
.title {
color: blue;
}
h1 {
color: black;
}
In this example the ID selector wins because its weight is highest, so the element with ID main-title becomes red even though other rules also offer colors.
The box model and display types
Each element on a page behaves like a small cardboard box with content in the center, padding around that content, borders around the padding and margins around the outside. This is the box model. Once you picture elements as boxes, layout becomes easier to reason about. The browser also assigns each element a display type such as block, inline or inline-block. These types determine how boxes sit next to each other, whether they claim full rows or flow inside lines and how their dimensions behave.
p {
margin: 1rem 0;
padding: 0.5rem;
border: 1px solid grey;
display: block;
}
Adding the first styles to the homepage
With the basics in place you can begin shaping the homepage. A few gentle touches already lift the page. You might adjust the background, set a readable font stack or change the spacing between sections. These first steps give the site a tone, almost like tuning an instrument before a performance.
body {
font-family: system-ui, sans-serif;
background-color: #fdfdfd;
color: #222;
}
header h1 {
font-size: 2rem;
margin-bottom: 0.5rem;
}
The history of CSS adds an interesting final thread to this chapter. HTML grew mostly from the hands of programmers who wanted a simple language for documents on a young and experimental web. Its early focus stayed close to structure and meaning, which is why the core tags read like the bones of a clean outline. CSS arrived later from a different crowd entirely.
Many of its ideas came from designers who already knew how to shape pages in print and who wanted the web to offer the same quiet control over spacing, type, rhythm and proportion. Their influence gave CSS its visual vocabulary and its careful separation from HTML. One language holds meaning and order, and the other handles the look and feel of the page. Together they form a steady partnership that still guides how pages are built today.
Chapter 11: Typography, Color, and Spacing
Good design often begins with quiet choices rather than loud tricks. A page becomes pleasant when text breathes, colors support the message and spacing forms gentle paths for the eye to follow. These ideas shape this chapter. You will explore type that works on every screen, color that stays readable and spacing that gives each element a little room to live. By combining these pieces you can shape pages that feel calm and inviting even before the more advanced layout tools arrive.
Readable text for all screens
Text sits at the heart of almost every website, so giving it care is one of the quickest ways to lift a page. Good typography feels like a clear voice in a quiet room. Readers should glide through paragraphs without noticing the shape of the letters too much. That ease comes from three things: a sensible font choice, steady sizing and spacing that lets the lines breathe.
Most devices ship with reliable system fonts, yet each platform draws them with slightly different curves and weights. A font stack lets you guide that choice by listing several families in order. The browser picks the first one it knows, which means your page keeps a consistent personality even when viewed on many screens. Stacks can hint at the tone of your site: rounded sans serif fonts feel friendly, while serif fonts carry a more traditional or bookish character.
Many beginners also explore hosted fonts such as those provided by Google Fonts (fonts.google.com). These give you access to hundreds of well-crafted families that you can include with a single link in your HTML. Choosing a hosted font can help support branding because you can match the tone to your project, whether you want playful curves, steady professionalism or a neutral voice that stays out of the way.
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap">
After linking a hosted font you simply reference it in your CSS. The browser downloads the files automatically and applies them just like any local typeface. This makes it possible to keep typography consistent across phones, tablets, laptops and even smart TVs.
body {
font-family: "Roboto", system-ui, sans-serif;
font-size: 1rem;
line-height: 1.6;
}
Size also matters. Browsers set a default of roughly sixteen pixels for base text, which is usually a good starting point because it remains readable across many screen sizes. Larger sizes can feel relaxed and welcoming, though they may crowd smaller screens. Smaller sizes can feel efficient yet risk forcing readers to squint. The safest path is to work from the default and adjust only when a section truly needs it.
Beginner pages sometimes use too many styles at once, mixing italics, bold weights and decorative faces. This can feel noisy. Instead choose one main font for body text and a second for headings if you want a contrast in tone. Keeping the number low helps the page feel coherent and makes the overall rhythm predictable for readers.
Color choices and contrast
Color shapes the tone of a page and the comfort of the reading experience. It can steer attention toward key elements, soften the look of a layout or strengthen the sense of personality behind a site. To use color well, you need both creative intention and a little practical knowledge about how the browser understands color values. Once these pieces come together you can build palettes that feel balanced, readable and consistent across all screens.
CSS accepts several ways of writing colors. The shortest form uses three hexadecimal digits such as #abc. Each digit expands behind the scenes into a pair… so #abc becomes #aabbcc. The six digit form, such as #1a4f9e, is more common because it offers full control over red, green and blue values. These values always sit in pairs, ranging from 00 for none to ff for full strength. Hex codes are precise and easy to adjust when you need fine control.
Hex colors store their values in a clear order. The first part always represents the red channel, the second part represents green and the third part represents blue. In a three digit code such as #abc, each digit expands into a pair… so a becomes aa, b becomes bb and c becomes cc. In a six digit code such as #33aa55, the pairs appear openly. The first two digits 33 set the red value, the next two aa set the green value and the final two 55 set the blue value. Because hex uses base sixteen, the range runs from 00 for no intensity to ff for full intensity, which gives you smooth control over every channel.
You can also use plain English color names such as red, white or mediumseagreen. Named colors are quick to type, yet they offer less accuracy than hex values. They work well for simple designs or early experiments. Once you begin refining a palette, most designers switch to hex codes or functional color formats such as rgb() and rgba().
Transparency becomes possible with rgba() or its newer cousin hsl() and hsla(). The final value in these functions controls opacity. A low opacity creates soft overlays, tinted backgrounds or subtle shadows. This makes transparency a powerful tool for building depth without overwhelming the page.
/* Hex codes */
.header {
background-color: #224488;
}
/* Short hex */
.badge {
background-color: #fa0;
}
/* Named color */
.notice {
color: darkred;
}
/* Transparent overlay */
.overlay {
background-color: rgba(0, 0, 0, 0.25);
}
Contrast still sits at the center of color design. Readers need a clear difference between text and its background. The easiest approach is to start with one base color for text, such as a deep charcoal, and then choose a light neutral for the background. When you want to build a palette, select one main hue for accents and then create lighter or darker variations by adjusting brightness, saturation or opacity. This keeps the palette consistent while offering enough variety for headings, links and panels.
Matching or complementary colors help keep the design harmonious. Colors that sit near each other on the color wheel feel calm and friendly, while colors that sit opposite each other feel energetic and bold. Both approaches work as long as contrast stays strong where text is involved. A bright accent against a muted surface draws the eye, yet bright text on a bright background leads to strain.
Color tools and browser extensions can inspect any value on your page and report how well it meets accessibility guidelines. These guidelines do not limit creativity. Instead they help ensure that your design remains readable for as many people as possible, especially in long passages of text or when viewed outdoors on bright screens.
Margins, padding, and balanced layout
Spacing shapes the quiet architecture of a page. Margins create gaps between elements while padding creates gaps inside them. When these spaces repeat in steady patterns the entire layout feels comfortable. Without them, everything crowds together as if the page is whispering too fast. A simple spacing scale helps keep measurements consistent so headings, paragraphs and images never feel out of place.
section {
margin: 2rem 0;
}
p {
margin: 1rem 0;
}
.card {
padding: 1rem;
border: 1px solid #ddd;
}
Backgrounds, borders, and shadows
These three tools define edges and layers. Backgrounds can highlight blocks of content or create subtle depth. Borders outline shapes and make structure visible. Shadows add a hint of lift, almost like sunlight slipping beneath a sheet of paper. When used gently they help readers understand how pieces relate to each other. When used heavily they can overwhelm, so restraint keeps the effect charming rather than loud.
There is also text-shadow, which applies a soft outline or glow to letters. It can improve readability when text sits on a busy background, though it works best in very small doses. A faint shadow can give headings a sense of presence, but strong or repeated shadows can make text blur or shimmer. As with all visual effects, clarity comes first and decoration comes second.
.panel {
background-color: #fff;
border: 1px solid #ccc;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 1rem;
}
h2 {
text-shadow: 1px 1px 2px rgba(0,0,0,0.15);
}
border and border-radius for images
Simple image borders use the same properties as any other box. You can set width, style and color on the img element. Rounded corners soften the edge and help photos feel less boxy. Keep the thickness modest so the picture remains the focus.
img.photo {
display: block;
max-width: 100%;
height: auto;
border: 4px solid #ccc;
border-radius: 8px;
}
display: block and set height: auto so images scale cleanly while the border keeps its shape.
border-image for frames and certificates
For decorative frames, use border-image. This property takes a single graphic (PNG, SVG or gradient), slices it into nine regions (four corners, four edges and the center) and paints those pieces around your element. Corners stay crisp while edges can repeat or stretch, which lets you create picture frames and certificate borders from one source file.
img.framed {
display: block;
max-width: 100%;
height: auto;
/* Reserve border space first */
border: 24px solid transparent;
/* Provide the artwork and slice it */
border-image-source: url("frame-ornate.png");
border-image-slice: 24;
border-image-width: 24;
border-image-repeat: round;
}
The border-image-slice value tells the browser how far to cut in from each edge. Those cuts create the nine-region grid. The corners draw once. The top, right, bottom and left edges repeat or stretch according to border-image-repeat. The center region is ignored for images, so your picture remains visible.
border must be nonzero for border-image to show. Use a transparent border color when the artwork itself provides the color.
Controlling edges and corner
Use a single number to slice all sides equally or four numbers to fine tune each side. Choose stretch when artwork should expand, repeat to tile exact copies or round to tile and adjust spacing so tiles fit evenly.
.frame-wood {
border: 20px solid transparent;
border-image-source: url("wood-grain.png");
border-image-slice: 20;
border-image-repeat: repeat;
}
.frame-ribbon {
border: 16px solid transparent;
border-image-source: url("ribbon.svg");
border-image-slice: 24 18 24 18;
border-image-repeat: stretch;
}
A reusable frame utility
You can turn a frame into a utility class and apply it to any element, not just images. This works for certificates, pull quotes and callouts. The element’s inner content remains untouched because the frame only paints the border box.
.u-frame {
border: 28px solid transparent;
border-image-source: url("certificate-border.svg");
border-image-slice: 28;
border-image-width: 28;
border-image-repeat: round;
background-color: #fff;
padding: 1.25rem;
}
<figure class="u-frame">
<img src="award.jpg" alt="Certificate recipient smiling with award">
<figcaption>Community Award, 2025</figcaption>
</figure>
Borders with gradients for subtle mats
You can create gentle picture mats without images by combining a transparent border with a CSS gradient as the border source. This keeps page weight low while giving a refined edge.
.mat-soft {
border: 12px solid transparent;
border-image-source: linear-gradient(90deg, #e9e9e9, #f7f7f7);
border-image-slice: 1;
border-image-repeat: stretch;
background-color: #fff;
}
These techniques let you finish a layout with elegant frames. Use them sparingly so images stay the hero and the design remains calm and readable.
Using gradients for depth and gentle color transitions
Gradients appear often in modern design because they let you blend colors smoothly without relying on image files. They can add quiet depth behind sections, soften the edges of panels or create subtle bands of tone that guide the eye. Since gradients are generated by the browser, they scale cleanly across all screen sizes and never blur.
CSS offers two main types: linear-gradient() and radial-gradient(). A linear gradient shifts color along a straight line. A radial gradient spreads outward from a center point, almost like ink drifting through water. Both can use any colors you choose, and both accept multiple stops where the tone changes.
.hero {
background-image: linear-gradient(
135deg,
#f7f7f7,
#e0e0e0 40percent,
#cccccc
);
padding: 2rem;
}
Angles add direction. A 90deg gradient moves from left to right, while 180deg moves from top to bottom. Using percentages for color stops gives you precise control over where each shift happens. These transitions can be bold for decorative banners or gentle for panels that support text.
.spotlight {
background-image: radial-gradient(
circle,
rgba(255, 255, 255, 0.9),
rgba(255, 255, 255, 0) 70percent
);
padding: 1.5rem;
}
Radial gradients work well for soft highlights because the brightness fades naturally as it expands. This makes them useful behind headings or callouts. Keeping the opacity low helps the effect stay calm rather than theatrical.
Because gradients are resolution independent, they are lighter to load than images and remain crisp on high density screens. They can also replace heavy decorative assets in certificates, dividers and hero sections, giving your layouts polish without increasing page weight. Adding gradients to your toolkit offers a final, flexible way to give pages depth and character.
Chapter 12: Selectors, Pseudo-Classes, and Pseudo-Elements
Selectors are the language you use to point at parts of a page. They decide which elements receive which rules, forming the bridge between structure and design. Once you understand how selectors read the document and how pseudo-classes and pseudo-elements extend that reach, you gain a level of precision that makes your CSS feel almost conversational. This chapter explores the most common selectors, the flexible world of attribute targeting and the expressive tools that let you style states, highlights and tiny details without changing your HTML.
Common selectors
Basic selectors form the foundation of most stylesheets. They include element selectors such as p and nav, class selectors such as .card and ID selectors such as #intro. These three types appear constantly because they map directly to the structure of the HTML. When combined, they let you narrow or broaden your reach with ease.
p {
margin: 1rem 0;
}
.card {
padding: 1rem;
border: 1px solid #ccc;
}
#intro {
font-size: 1.25rem;
}
Descendant selectors target elements that sit somewhere inside other elements. They use spaces to show each step in the path. A space means “contained within”, not “listed alongside”. If you used commas instead, the selector would simply become a list of separate targets rather than a chain. Reading a descendant selector feels like walking through nested boxes until you reach the element you want.
nav ul li a {
text-decoration: none;
color: #0044aa;
}
Attribute selectors
Attribute selectors allow you to style elements based on the presence or value of their attributes. This can be especially helpful for forms, links, data tables and custom structures where classes alone might not explain the purpose of each element. By matching attributes, your CSS gains a little more insight into what an element represents.
a[target="_blank"] {
color: #cc0000;
}
input[type="email"] {
border-color: #4488cc;
}
[data-level="warning"] {
background-color: #fff4e0;
}
Pseudo-classes and pseudo-elements
Pseudo-classes describe temporary or structural states such as hovering, focusing, selecting or visiting a link. They behave almost like small conditionals inside your stylesheet. When a user interacts with an element or when an element sits in a particular situation, the browser applies the matching rule.
a:visited {
color: purple;
}
input:focus {
outline: 2px solid #0057cc;
}
Pseudo-elements reach into parts of an element that do not exist as separate nodes in the HTML. They let you style the first letter of a paragraph, add decorative content or mark the beginning of sections with small symbols. This makes it possible to enrich a design without adding extra markup.
p::first-line {
font-weight: bold;
}
.article-title::after {
content: " ✦";
color: #888;
}
Common pseudo-classes
Pseudo-classes describe conditions or states that an element can be in. Some relate to user interaction, some to document structure and others to form behaviour or link history. This list gathers the most commonly used pseudo-classes so you can see the landscape at a glance. It is not every pseudo-class ever created, yet it covers the ones you will meet in everyday work.
Interaction and user input
:hoverwhen a pointing device rests over an element:activewhen an element is being pressed:focuswhen an element has keyboard or programmatic focus:focus-visiblewhen focus should be visibly indicated:focus-withinwhen an element or any of its descendants has focus
Link history
:linkan unvisited link:visiteda link that has been visited
Form and validation states
:checkedradio buttons and checkboxes that are selected:indeterminatetri-state checkboxes:disabledelements that are disabled:enabledthe opposite of:disabled:requiredform controls marked as required:optionalform controls that are not required:validinputs that pass validation:invalidinputs that fail validation
Structural pseudo-classes
:first-childthe first child of its parent:last-childthe final child of its parent:nth-child(n)matches the nth child:nth-last-child(n)counts from the end:first-of-typefirst element of its type inside a parent:last-of-typelast element of its type:nth-of-type(n)nth element of its type:only-childwhen an element is the sole child:only-of-typewhen an element is the sole child of that type:emptyelements with no children at all
State and logic
:rootthe top element of the document (thehtmltag):not(...)matches everything except what is inside the brackets:is(...)simplifies writing long selector lists:where(...)similar to:isbut always low specificity:has(...)parent selector that matches when descendants meet a condition
Content and media
:fullscreenwhen an element is in fullscreen mode:targetwhen an element is the target of a URL fragment:playingaudio or video currently playing:pausedaudio or video paused
These pseudo-classes give you a vocabulary for states that HTML cannot express alone. Combined with pseudo-elements, they let your styles react to structure, interactivity and user behaviour without adding a single extra element to your markup.
Hover and focus states that feel natural
Interactions should feel steady and reassuring. A good hover state lets readers know which elements respond to clicks or taps. A good focus state supports keyboard navigation and helps assistive technologies reveal where attention currently sits. These states work best when they are clear but not abrupt. Soft color changes, gentle underlines or subtle shadows guide the user without startling them.
a:hover {
text-decoration: underline;
}
button:hover {
background-color: #e8e8e8;
}
button:focus {
outline: 3px solid #2a7fff;
outline-offset: 2px;
}
Chapter 13: Flexbox for One-Dimensional Layout
Flexbox lets you arrange elements along a single axis with precise control over spacing and alignment. It excels at components such as navigation bars, toolbars and card rows. You work with a flex container that controls the flow of its flex items. This chapter explains the essential properties you will use daily, then builds a practical navigation bar to lock in the ideas.
The flex container
A flex layout begins when you set display:flex on a parent element. This parent becomes the flex container, and its direct children become flex items. The container defines direction, wrapping and spacing rules. The items respond to those rules and can also override some behaviour with their own properties.
display:flex creates the layout context
Setting display:flex activates the flex formatting context. Items line up on a single row by default; if there is not enough room they will overflow unless you also enable wrapping. The container takes control of alignment with properties such as justify-content, align-items and gap.
<style>
.container { display:flex; }
.container > div { padding:0.5rem; border:1px solid #ccc; }
</style>
<div class="container">
<div>One</div><div>Two</div><div>Three</div>
</div>
Main axis and cross axis
The main axis follows flex-direction. With row the main axis runs horizontally left to right. With column it runs vertically top to bottom. The cross axis is perpendicular to the main axis. Understanding these two directions makes alignment properties feel straightforward.
| flex-direction | Main axis | Cross axis |
row | Left to right | Top to bottom |
row-reverse | Right to left | Top to bottom |
column | Top to bottom | Left to right |
column-reverse | Bottom to top | Left to right |
<style>
.stack { display:flex; flex-direction:column; gap:0.5rem; }
</style>
<div class="stack">
<button>Save</button><button>Export</button><button>Close</button>
</div>
Wrapping with flex-wrap
Flexbox defaults to a single line of items. Add flex-wrap:wrap to allow items to flow to the next line when space runs out. Combine with gap for clean spacing that respects the wrap.
<style>
.tags { display:flex; flex-wrap:wrap; gap:0.5rem; }
.tags a { padding:0.25rem 0.5rem; background:#f3f3f3; text-decoration:none; border-radius:4px; }
</style>
<p>Topics:</p>
<nav class="tags">
<a href="#">CSS</a><a href="#">Flexbox</a><a href="#">Layout</a><a href="#">Typography</a><a href="#">SVG</a><a href="#">Forms</a>
</nav>
gap with side margins on the same items. Prefer a single spacing system to keep layouts predictable.
Horizontal and vertical alignment
Flexbox gives you fine control over alignment with a small set of properties. Use justify-content for spacing along the main axis and align-items for alignment on the cross axis. When wrapping is enabled, align-content controls how the rows or columns of items distribute space across the cross axis.
justify-content controls main-axis spacing
Use justify-content to position items across the main axis. Common values include flex-start, center, space-between and space-around. The spacing values distribute free space between items so you do not have to juggle margins.
<style>
.toolbar { display:flex; justify-content:space-between; align-items:center; padding:0.5rem; border:1px solid #ddd; }
.toolbar .left, .toolbar .right { display:flex; gap:0.5rem; }
</style>
<div class="toolbar">
<div class="left"><button>New</button><button>Open</button></div>
<strong>Project</strong>
<div class="right"><button>Share</button><button>Export</button></div>
</div>
align-items aligns on the cross axis
Use align-items to line up item edges or centers on the cross axis. Values such as stretch, flex-start, center and flex-end handle the common cases. This is helpful when items have different heights or when text and icons need to sit on the same baseline visually.
<style>
.media { display:flex; align-items:center; gap:0.75rem; }
.media img { width:48px; height:48px; border-radius:50%; }
</style>
<div class="media">
<img src="avatar.png" alt="Profile">
<p>Hello from the profile card. This text aligns vertically with the image center.</p>
</div>
align-content for wrapped lines
When items wrap into multiple lines, align-content distributes the stack of lines along the cross axis. This does nothing when the layout has a single line. Values mirror those of justify-content which keeps the mental model consistent.
<style>
.gallery { display:flex; flex-wrap:wrap; gap:0.5rem; align-content:start; height:200px; border:1px dashed #bbb; padding:0.5rem; }
.gallery > div { flex:0 0 90px; height:60px; background:#e9eef6; }
</style>
<div class="gallery">
<div></div><div></div><div></div><div></div><div></div><div></div><div></div><div></div>
</div>
Per-item control
You can let items push apart by giving one or more items margin-left:auto or margin-inline-start:auto in a row layout. For cross-axis overrides use align-self on an item to differ from the container’s align-items.
<style>
.bar { display:flex; align-items:center; gap:0.5rem; }
.bar .spacer { margin-left:auto; }
.bar button:last-child { align-self:flex-end; }
</style>
<div class="bar">
<button>A</button><button>B</button><span class="spacer"></span><button>C</button>
</div>
gap for consistent spacing, then use auto margins for strategic pushes such as separating primary and secondary actions.
Turning navigation into a flexible bar
A navigation bar is a perfect showcase for Flexbox. The container holds a brand on the left, links in the middle and a small group of actions on the right. The layout adapts smoothly across screen sizes and can wrap if space is tight.
HTML structure for a simple nav
Keep the markup clear. The container uses nav with three groups. The middle group is an unordered list of links. The right group holds small actions such as a search button or a theme toggle. Unknown or optional actions can be represented as … in examples when the exact content is not the focus.
<nav class="site-nav" aria-label="Primary">
<a class="brand" href="#">Brand</a>
<ul class="links">
<li><a href="#">Home</a></li>
<li><a href="#">Products</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
<div class="actions">
<button aria-label="Search">Search</button><button>Sign in</button>
</div>
</nav>
Flexbox CSS for layout, spacing and wrapping
The navigation container becomes a flex context. The link list is also a flex row so the list items sit horizontally with clean gaps. A flexible spacer is not required when you can use justify-content:space-between on the container, but a targeted margin:auto on the middle group often reads clearly.
<style>
.site-nav { display:flex; align-items:center; gap:1rem; padding:0.5rem 1rem; border-bottom:1px solid #e5e5e5; flex-wrap:wrap; }
.site-nav .brand { font-weight:bold; text-decoration:none; }
.site-nav .links { list-style:none; display:flex; gap:1rem; padding:0; margin:0; }
.site-nav .links a { text-decoration:none; }
.site-nav .actions { display:flex; gap:0.5rem; margin-left:auto; }
@media (max-width:640px) {
.site-nav { gap:0.5rem; }
.site-nav .links { flex-basis:100%; order:1; justify-content:center; }
.site-nav .actions { order:2; margin-left:0; width:100%; justify-content:center; }
}
</style>
Handling long labels and overflow
Real menus gain long labels. Prevent crowding by allowing the link list to wrap and by setting sensible minimums. You can also let one item take spare space with flex:1 when appropriate, such as a search input that can grow.
<style>
.site-nav .links li { min-width:6ch; }
.site-nav .search { flex:1; display:flex; }
.site-nav .search input { flex:1; }
</style>
<nav class="site-nav" aria-label="Primary">
<a class="brand" href="#">Brand</a>
<ul class="links">
<li><a href="#">Documentation</a></li>
<li><a href="#">Community</a></li>
<li class="search"><input type="search" placeholder="Search"></li>
</ul>
<div class="actions"><button>Sign in</button></div>
</nav>
Quick reference to core container properties
These are the container properties you will use frequently. Learn them as a set and layouts become easier to reason about.
| Property | Purpose | Typical values |
display | Enables the flex context | flex |
flex-direction | Sets main axis direction | row; column |
flex-wrap | Allows items to wrap | nowrap; wrap |
justify-content | Distributes items on main axis | flex-start; center; space-between |
align-items | Aligns items on cross axis | stretch; center; flex-start |
align-content | Distributes wrapped lines | flex-start; center; space-between |
gap | Sets consistent spacing between items | Length units such as 0.5rem |
With these fundamentals in place you can build flexible, robust components that adapt smoothly across screen sizes and content changes.
Chapter 14: CSS Grid for Fully Responsive Layout
Grid is a two-dimensional layout system that lets you place items in rows and columns with precision and ease. You define a grid on a container, then position children into cells or named areas. The same rules work for small screens and large screens; you can change track sizes, placement and gaps at different breakpoints without rewriting your HTML.
Grid areas and auto placement
A grid begins when a container uses display: grid. You then define columns and rows, and Grid will place items automatically unless you give them explicit coordinates. Named areas make layouts easier to read and maintain; the names describe intent and the browser handles the math.
Creating tracks
Tracks are the columns and rows of your grid. You can size them with fixed units, flexible fr units, or functions like minmax(). Start simple, then refine as your content and breakpoints demand.
.grid {
display: grid;
grid-template-columns: 1fr 2fr 1fr;
grid-template-rows: auto auto;
gap: 1rem;
}
This creates three columns (flexible in a 1:2:1 ratio) and two rows that size to their content. The gap property sets consistent spacing between grid cells.
Describing intent
Named areas make a layout read like a wireframe. Each quoted string represents a row; repeating a name spans that area across adjacent tracks.
.grid {
display: grid;
grid-template-columns: 200px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"sitehead sitehead"
"sidenav main"
"sitefoot sitefoot";
}
.sitehead { grid-area: sitehead; }
.sidenav { grid-area: sidenav; }
.main { grid-area: main; }
.sitefoot { grid-area: sitefoot; }
The CSS reads like a plan: header at the top across both columns, sidebar and main content in the middle, and footer across the bottom.
sitehead, main, and sitefoot; this keeps your template readable and your selectors concise.
Letting Grid place items
If you omit explicit positions and areas, Grid fills available cells in source order. You can influence the fill direction with grid-auto-flow and control sizes of implicit tracks that appear beyond your template.
.gallery {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 12rem;
grid-auto-flow: row; /* or column; or dense for backfilling */
gap: 0.75rem;
}
Here items flow by rows, each implicit row is 12rem tall, and the template defines three equal columns. Using dense can reduce gaps, but it may rearrange source order, which can affect reading order for assistive tech and keyboard navigation.
Responsive units and media queries
Grid shines when paired with flexible units; you can blend fr, percentages, and functions that adapt to available space. Media queries let you switch templates at logical breakpoints based on content needs.
Flexible tracks
minmax() gives a track a protective minimum and an elastic maximum. Combining it with fr and auto-fit or auto-fill yields rows of cards that wrap neatly on any screen.
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
gap: 1rem;
}
Each card gets at least 14rem of width and stretches up to a flexible fraction. The number of columns changes automatically as space allows.
When to use auto-fit versus auto-fill
Both keywords create as many columns as will fit. The difference appears when leftover space remains. auto-fit collapses empty tracks so existing items grow to fill the row; auto-fill preserves the tracks, which can be useful if you expect more items later.
.fit { grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr)); }
.fill { grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); }
Choose the one that matches your layout goals. If you want cards to stretch and avoid gaps, pick auto-fit. If you want a regular rhythm that anticipates more content, pick auto-fill.
Switching templates with media queries
Sometimes the relationship between regions changes at wider widths. You can keep a single source order and swap templates at a breakpoint that suits your content rather than a specific device.
.page {
display: grid;
grid-template-areas:
"sitehead"
"main"
"sidenav"
"sitefoot";
grid-template-columns: 1fr;
gap: 1rem;
}
@media (min-width: 48rem) {
.page {
grid-template-areas:
"sitehead sitehead"
"sidenav main"
"sitefoot sitefoot";
grid-template-columns: 16rem 1fr;
}
}
On small screens, regions stack in a single column. At 48rem and above, the sidebar moves beside the main content while header and footer span both columns.
A full page layout with grid
This example assembles a complete page using named areas, flexible tracks, and a single DOM structure. It remains readable on small screens and gains a two-column composition when space allows.
<header class="sitehead">
<h1>My Site</h1>
</header>
<nav class="sidenav" aria-label="Primary">
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Articles</a></li>
<li><a href="#">About</a></li>
</ul>
</nav>
<main class="main">
<article>
<h2>Latest</h2>
<p>Welcome to the site. Here is an update.</p>
</article>
</main>
<footer class="sitefoot">
<p>© <span id="year">2025</span></p>
</footer>
.page {
display: grid;
grid-template-areas:
"sitehead"
"main"
"sidenav"
"sitefoot";
grid-template-columns: 1fr;
gap: 1.25rem;
padding: 1rem;
}
.sitehead { grid-area: sitehead; }
.sidenav { grid-area: sidenav; }
.main { grid-area: main; }
.sitefoot { grid-area: sitefoot; }
@media (min-width: 56rem) {
.page {
grid-template-areas:
"sitehead sitehead"
"sidenav main"
"sitefoot sitefoot";
grid-template-columns: 18rem 1fr;
align-items: start;
}
}
.sitehead, .sidenav, .main, .sitefoot {
background: #f7f7f9;
border: 1px solid #e0e0e6;
padding: 1rem;
border-radius: 0.5rem;
}
Attach the page class to a wrapper element that contains the four regions. The content stacks naturally on small viewports; at wider widths the layout breathes and aligns to a familiar pattern.
Upgrading the photo gallery into a responsive mosaic
A gallery benefits from automatic wrapping and variable row heights. You can keep a simple markup list and let Grid create a masonry-style mosaic with minimal CSS by mixing fixed row heights and item spans.
Base grid for the gallery
Define equal columns with a flexible minimum and a sensible gap. Use a short implicit row height; items can then span rows to create natural variation.
.gallery {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
grid-auto-rows: 8rem;
gap: 0.75rem;
}
This layout adapts to screen width and maintains even spacing between images.
Spanning rows for natural variation
Apply a class to items that should be taller. The span value multiplies the implicit row height; this produces a pleasing mosaic that still aligns to an underlying rhythm.
.gallery figure { margin: 0; }
.gallery img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gallery .tall { grid-row: span 2; }
.gallery .grand { grid-row: span 3; }
<section class="gallery" aria-label="Photo gallery">
<figure class="tall"><img src="img1.jpg" alt="Sunlit forest"></figure>
<figure><img src="img2.jpg" alt="City skyline"></figure>
<figure class="grand"><img src="img3.jpg" alt="Mountain lake"></figure>
<figure><img src="img4.jpg" alt="Desert road"></figure>
<figure class="tall"><img src="img5.jpg" alt="Waves at dusk"></figure>
</section>
Use emphasis to mark special items when it makes the story clearer, then apply a span that matches their visual weight. Keep alt text short and meaningful so the gallery remains accessible.
Enhancing flow with subgrid when supported
Where supported, subgrid lets child grids align with parent tracks, which keeps gutters and vertical rhythm consistent across nested components. You can progressively enhance by checking support and falling back to a standard grid.
@supports (grid-template-rows: subgrid) {
.card-list { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1rem; }
.card { display: grid; grid-template-rows: subgrid; grid-row: span 3; }
}
Browsers that support subgrid will align nested tracks automatically; others will use the regular grid rules without breaking layout.
When you need general placeholders in examples, show omitted declarations clearly: selector { … }. This signals that other properties may be present without implying a specific set.
Chapter 15: Transformations, Transitions, and Animation
Movement on a page can feel gentle or jolting depending on how it is shaped. CSS gives you a small toolkit for motion that works across modern browsers with very little code. You can nudge an element, fade a panel, or choreograph a full sequence with keyframes. Used with care, these effects help readers understand change rather than distract them. This chapter explores the building blocks of smooth transitions and small animations that enhance rather than overwhelm.
Smooth transitions
A transition eases a change from one state to another. This could be a button shifting color on hover or a card lifting slightly when focused. Transitions happen automatically when a property changes; you define the duration, the delay and the timing function that shapes the movement. Humans tend to prefer motion that starts gently, accelerates a little, then settles softly. This is why ease-in-out often feels natural in interface work.
Timing functions shape the movement
Timing functions describe the rate of change over time. A linear transition moves at a steady pace. An ease-in transition starts slowly then speeds up. An ease-out starts quickly then slows at the end. Ease-in-out blends both behaviours into a smooth middle curve that often matches the way objects feel in the physical world.
.button {
background: #0066cc;
color: white;
padding: 0.5rem 1rem;
transition: background 0.25s ease-in-out;
}
.button:hover {
background: #004c99;
}
The shift is small, but the timing curve shapes it into something that feels intentional rather than abrupt.
Transitioning multiple properties
When several properties change together, a combined transition keeps them in sync. You can target each property with its own duration and curve or keep a unified timing if the motion should feel like a single action.
.card {
border-radius: 0.5rem;
transform: scale(1);
transition:
transform 0.2s ease-out,
box-shadow 0.2s ease-out;
}
.card:hover {
transform: scale(1.03);
box-shadow: 0 0.5rem 1rem rgba(0,0,0,0.15);
}
Small scale adjustments paired with a soft shadow create the sense of lifting an object slightly off the page.
Simple transforms and motion
Transforms let you shift, rotate, scale or skew an element without affecting the layout around it. These operations are handled by the browser’s compositor and usually feel smooth even on constrained devices. Because transforms do not force reflow, they are ideal for motion that needs to remain responsive.
Moving, scaling and rotating elements
You combine transform functions to create small, expressive moments. These do not require special syntax; the browser composes them into a single transform matrix.
.tile { transition: transform 0.3s ease-in-out; }
.tile:hover {
transform: translateY(-0.25rem) scale(1.02);
}
This tiny lift is enough to signal that an element is interactive while staying modest in motion.
Perspective and 3D transforms
CSS also supports 3D transforms. With perspective and functions such as rotateX() and rotateY(), you can tilt panels or flip cards. These effects require restraint because large rotations can feel abrupt or disorienting unless they serve a clear purpose.
.flip {
transform-style: preserve-3d;
transition: transform 0.6s ease-in-out;
}
.flip:hover {
transform: rotateY(180deg);
}
Use subtle angles for UI work and keep full flips for components that truly benefit from a dramatic reveal.
Animation with keyframes
Transitions only animate between two states. Keyframes open the door to multi-step motion. You define a sequence of frames that vary properties over time; the browser interpolates the steps. Complex effects such as looping pulses, sliding panels or gentle reveals become possible with just a few lines of CSS.
Defining keyframes
A keyframe animation describes how a property changes from the start to the end. Use percentages or keywords like from and to. Any animatable property can appear inside the frames.
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.06); }
100% { transform: scale(1); }
}
.pulse {
animation: pulse 1.5s ease-in-out infinite;
}
This creates a slow breathing motion that works well for quiet emphasis without demanding attention.
Controlling playback
The animation shorthand includes duration, delay, timing function, iteration count and direction. For one-off entrances you often keep the count to a single run. For looping shapes such as loading indicators you might choose infinite repetition.
.spinner {
width: 1.5rem;
height: 1.5rem;
border: 0.25rem solid #ccc;
border-top-color: #0066cc;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
The result is light and efficient because only the rotation is being animated.
Making gallery images come alive
A gallery benefits from tiny moments of motion that respond to focus, hover or in-view states. These movements support the content rather than compete with it. A gentle zoom or a tilt can highlight an image and help readers sense that the item is interactive.
A soft zoom that respects layout
Using scale inside a clipping container keeps the movement clean. The image grows slightly within its box without pushing surrounding items.
.gallery figure {
margin: 0;
overflow: hidden;
border-radius: 0.5rem;
}
.gallery img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.35s ease-in-out;
}
.gallery figure:hover img,
.gallery figure:focus-within img {
transform: scale(1.06);
}
This effect looks polished and remains performant because only transforms are animated.
A tilt for personality
A small perspective tilt adds personality, but the angle must remain subtle. Large rotations distract quickly. When used lightly, the movement feels playful yet controlled.
.tilt {
transform: rotateZ(0deg);
transition: transform 0.3s ease-out;
}
.tilt:hover {
transform: rotateZ(2deg);
}
The rotation is small, almost like a photo leaning slightly on a shelf.
Entrance animations that do not overwhelm
If you want images to ease into view, pair opacity with a downward translate. Keep the duration short so the sequence supports scanning rather than slowing it.
@keyframes floatin {
from { opacity: 0; transform: translateY(0.75rem); }
to { opacity: 1; transform: translateY(0); }
}
.gallery figure {
animation: floatin 0.5s ease-out;
}
The image drifts into place with a soft landing. The effect stays quiet even in a large set of photos.
Chapter 16: Styling Forms and UI Elements
Forms are where design meets interaction. When a reader types into a field or taps a button, the smallest visual details affect confidence and speed. In this chapter you will learn how to shape inputs that feel clear, readable and well spaced. You will also learn to communicate state with focus and validation, craft accessible custom controls and assemble a polished contact page that works across devices.
Visually clear input fields
Good form fields are easy to scan and understand at a glance. Labels need a clear relationship to their inputs; spacing must guide the eye from one step to the next; touch targets should be comfortable. Begin with consistent font sizes, generous line height and a layout that does not rely on color alone to explain meaning.
A sensible base for <input> and <label>
This base style sets a readable type scale, spacing and clear borders. It also uses system fonts to keep rendering crisp on every platform.
/* Base form styles */
form { max-width: 42rem; margin: 0 auto; padding: 1rem; }
label { display: block; font-weight: 600; margin-bottom: 0.25rem; }
input[type="text"],
input[type="email"],
input[type="tel"],
input[type="password"],
textarea,
select {
width: 100%;
padding: 0.6rem 0.75rem;
border: 1px solid #c9ccd1;
border-radius: 0.5rem;
font: 1rem/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background-color: #fff;
color: #1a1f2a;
}
textarea { min-height: 7rem; resize: vertical; }
.form-row { margin-bottom: 1rem; }
Helpful placeholder and hint text
Placeholders should be suggestions, not instructions. If you need to guide the user, add a short hint under the field. Keep the wording brief and specific.
.hint { font-size: 0.9rem; color: #5a6473; margin-top: 0.35rem; }
States for focus and validation
Every interactive control should make its state obvious. Focus shows where the keyboard will act next; validation shows whether the value is acceptable. Use shape, contrast and a small amount of motion to communicate state without confusion.
Accessible focus
Use a strong outline that contrasts with the background. :focus-visible reduces visual noise for pointer users while retaining a clear indicator for keyboard users.
input:focus-visible,
textarea:focus-visible,
select:focus-visible {
outline: 3px solid #3b82f6;
outline-offset: 2px;
border-color: #3b82f6;
transition: outline-offset 120ms ease, border-color 120ms ease;
}
Clear validation
Native HTML validation handles many cases. Use it to color borders, then provide concise messages that explain how to fix issues. Avoid relying on color alone; include an icon or short text where possible.
input:required:invalid { border-color: #e11d48; }
input:required:valid { border-color: #22c55e; }
.error { color: #b91c1c; font-size: 0.9rem; margin-top: 0.35rem; }
.success { color: #166534; font-size: 0.9rem; margin-top: 0.35rem; }
Custom checkboxes and toggles
Custom controls can look refined and still remain accessible. Keep the native input in the document for semantics, then style a visual replacement using an adjacent element. Ensure labels toggle the control and that focus styles are still visible.
A styled checkbox that keeps native semantics
This pattern hides the default box and draws a custom box using ::before. The input remains focusable and screen readers still understand it.
.check {
display: inline-flex;
align-items: center;
gap: 0.5rem;
cursor: pointer;
}
.check input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 1px;
height: 1px;
}
.check .box {
width: 1.1rem;
height: 1.1rem;
border: 2px solid #475569;
border-radius: 0.25rem;
display: inline-block;
position: relative;
}
.check input[type="checkbox"]:focus-visible + .box { outline: 3px solid #3b82f6; outline-offset: 2px; }
.check input[type="checkbox"]:checked + .box::before {
content: "";
position: absolute;
inset: 0.1rem;
background: #16a34a;
border-radius: 0.2rem;
}
A smooth on off toggle
A toggle switch can be built from a checkbox and a rounded track with a sliding knob. Keep the motion brief; fast changes feel responsive and do not distract.
.toggle { display: inline-flex; align-items: center; gap: 0.5rem; cursor: pointer; }
.toggle input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.toggle .track {
width: 2.5rem;
height: 1.4rem;
background: #cbd5e1;
border-radius: 1rem;
position: relative;
transition: background 160ms ease-in-out;
}
.toggle .knob {
position: absolute;
top: 0.15rem;
left: 0.15rem;
width: 1.1rem;
height: 1.1rem;
background: #fff;
border-radius: 50%;
box-shadow: 0 1px 2px rgba(0,0,0,.2);
transition: left 160ms ease-in-out;
}
.toggle input:checked + .track { background: #22c55e; }
.toggle input:checked + .track .knob { left: 1.25rem; }
.toggle input:focus-visible + .track { outline: 3px solid #3b82f6; outline-offset: 2px; }
<label>. This expands the hit area; taps on text toggle the control which feels natural on touch screens.
A polished contact page
Now bring the pieces together. This layout groups fields, shows clear focus and validation, and uses a button style that looks clickable without relying on strong color alone. It is compact on phones and comfortable on larger screens.
<form action="/contact" method="post" novalidate>
<div class="form-row">
<label for="name">Your name</label>
<input id="name" name="name" type="text" autocomplete="name" required placeholder="Ada Lovelace">
<p class="hint">Please use your full name.</p>
<p class="error" aria-live="polite" hidden>Enter your name.</p>
</div>
<div class="form-row">
<label for="email">Email address</label>
<input id="email" name="email" type="email" autocomplete="email" required placeholder="name@example.com">
<p class="error" aria-live="polite" hidden>Enter a valid email address.</p>
</div>
<div class="form-row">
<label for="topic">Topic</label>
<select id="topic" name="topic" required>
<option value="">Select a topic…</option>
<option>Speaking request</option>
<option>Project inquiry</option>
<option>General feedback</option>
</select>
</div>
<div class="form-row">
<label class="check">
<input id="updates" name="updates" type="checkbox">
<span class="box"></span>
Receive occasional updates
</label>
</div>
<div class="form-row">
<label for="message">Message</label>
<textarea id="message" name="message" required placeholder="How can we help…"></textarea>
<p class="error" aria-live="polite" hidden>Please enter a message.</p>
</div>
<div class="form-row">
<label class="toggle">
<input id="urgent" name="urgent" type="checkbox">
<span class="track"><span class="knob"></span></span>
Mark as urgent
</label>
</div>
<div class="form-row">
<button class="btn" type="submit">Send message</button>
</div>
</form>
/* Button that feels clickable without shouting */
.btn {
appearance: none;
display: inline-block;
padding: 0.7rem 1.1rem;
border-radius: 0.6rem;
border: 1px solid #0f172a;
background: linear-gradient(#0b63f6, #0952c6);
color: #fff;
font-weight: 600;
letter-spacing: 0.25px;
cursor: pointer;
transition: transform 80ms ease, filter 120ms ease;
}
.btn:hover { filter: brightness(1.05); }
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: 3px solid #3b82f6; outline-offset: 3px; }
For client side validation you can toggle message visibility with a small script that checks field validity after each input event. Keep logic focused and avoid blocking submission for small formatting differences when the message is still understandable.
<script>document.querySelectorAll("form [required]").forEach(function(el){
el.addEventListener("input", function(){
var error = el.closest(".form-row").querySelector(".error");
if (!error) return;
var invalid = el.value.trim() === "" || (el.type === "email" && !el.checkValidity());
error.hidden = !invalid;
});
});</script>
Well designed forms reduce friction. Clear labels, visible focus, concise validation and controls that feel tactile will help readers complete tasks with confidence and ease.
Chapter 17: CSS Architecture and Reusable Patterns
As projects grow, small style choices become structural decisions. This chapter introduces practical patterns for naming classes, shaping components, using CSS custom properties and organizing styles so that a site can expand without turning fragile. The goal is to create parts that are easy to find, easy to change and easy to reuse.
Class naming patterns
Clear class names make styles predictable. A name should hint at purpose, not just appearance, and it should follow a pattern that feels consistent from file to file. The following subsections show simple ways to keep names readable and scalable.
BEM and readable names
BEM stands for Block, Element and Modifier. A block is a standalone component; an element is a part of that component; a modifier expresses a variation. This structure reduces guesswork because the relationship is visible in the name itself.
.card { … }
.card__title { … }
.card__actions { … }
.card--featured { … }
Use full words for blocks and elements. Keep modifiers short and consistent, such as --large, --active and --muted.
Names that describe purpose instead of look
Names tied only to appearance tend to age quickly. Prefer intent such as .btn, .btn--primary and .notice instead of look only names such as .blue or .left. This leaves room for design changes without renaming classes across files.
/* Prefer intent */
.btn { … }
.btn--primary { … }
/* Avoid brittle names */
.blue { … }
.left { … }
Lightweight utility classes
Utilities are single purpose helpers such as spacing and layout nudges. They help compose designs without creating new components for tiny variations. Keep utilities short and consistent; document them in one place.
.u-m-0 { margin: 0; }
.u-px-2 { padding-left: .5rem; padding-right: .5rem; }
.u-flex { display: flex; }
.u-sr-only { position: absolute; width: 1px; height: 1px; clip: rect(0 0 0 0); overflow: hidden; }
State and behavior flags
Use clear flags for state that will change at runtime. Class flags such as .is-open, .is-active and .is-invalid make toggling styles easy for scripts and keep the style rule readable.
.accordion.is-open > .accordion__panel { display: block; }
.field.is-invalid input { border-color: var(--color-danger); }
Component level styling
A component should bundle structure, appearance and local variables in one place. This reduces side effects and makes reuse straightforward. The following subsections show practical patterns to keep components resilient.
Encapsulated blocks with predictable children
Define a parent class for each component, then target only its elements. This narrows the scope and limits accidental conflicts elsewhere on the page.
.card { border: 1px solid var(--surface-border); border-radius: .5rem; background: var(--surface); }
.card__title { margin: 0 0 .5rem 0; font-size: 1.125rem; }
.card__meta { font-size: .875rem; color: var(--text-muted); }
Composition with small utilities
Combine component rules with a few utilities when it keeps the stylesheet simpler. The component carries the meaning; the utility carries the tiny adjustment.
<article class="card u-m-0">
<h3 class="card__title">Title</h3>
<p class="card__meta">Meta</p>
</article>
@layer to control the cascade
The cascade can be made more predictable with layers. Place base rules first; then components; then utilities. Later layers win when selectors are equal in strength, which avoids overuse of !important.
@layer base, components, utilities;
@layer base {
:root { --text: #111; --surface: #fff; --surface-border: #e5e7eb; }
html, body { margin: 0; padding: 0; }
}
@layer components {
.card { … }
.btn { … }
}
@layer utilities {
.u-m-0 { margin: 0; }
.u-flex { display: flex; }
}
@layer with multiple files, ensure layer names match exactly in each file. A spelling mismatch creates a new layer and changes cascade order in ways that can surprise later.
Local tokens per component
Local tokens make components portable. Define private variables on the component root and reference them inside. This pattern keeps the interface small and clear.
.alert {
--alert-bg: var(--surface);
--alert-fg: var(--text);
--alert-border: var(--surface-border);
background: var(--alert-bg);
color: var(--alert-fg);
border: 1px solid var(--alert-border);
}
.alert--success { --alert-bg: #f0fdf4; --alert-border: #86efac; }
CSS variables and custom properties
CSS custom properties hold design tokens that the browser can inherit and update at runtime. They act a little like named storage in a programming language. A value is placed in a named slot and that name can be used wherever the value is needed. This helps keep designs consistent because the source of truth lives in one place. Custom properties support themes, user preferences and small context overrides without rewriting selectors.
Global tokens in :root
Place shared tokens in :root so that all components inherit the same base values. Keep names stable and group related values together.
:root {
--color-bg: #ffffff;
--color-fg: #111111;
--color-primary: #155eef;
--radius-regular: .5rem;
--space-2: .5rem; --space-3: .75rem; --space-4: 1rem;
}
Scoped overrides and theming
Wrap sections in a theme class and override only the tokens that change. This avoids duplication and keeps the component rules unchanged.
.theme-dark {
--color-bg: #0b1220;
--color-fg: #e5e7eb;
--color-primary: #93c5fd;
}
body { background: var(--color-bg); color: var(--color-fg); }
a { color: var(--color-primary); }
Fallbacks and computed values
Provide fallbacks with the second argument of var(). Use calc() with custom properties to derive consistent sizes from a single source.
.avatar {
width: var(--avatar-size, 40px);
height: var(--avatar-size, 40px);
border-radius: calc(var(--radius-regular) * 2);
}
User preference queries
Pair tokens with preference queries to respect motion and color choices without duplicating entire stylesheets.
@media (prefers-reduced-motion: reduce) {
:root { --motion-duration: 0ms; --motion-ease: linear; }
}
@media (prefers-color-scheme: dark) {
:root { --color-bg: #0b1220; --color-fg: #e5e7eb; }
}
Managing CSS as a site grows
Growth brings new pages, new contributors and new requirements. A few processes keep the stylesheet healthy: file structure, consistent layers, linting, naming rules and periodic pruning.
Layered file structure
Group styles by role, not by page. Keep base rules separate from components and utilities; this mirrors the cascade and keeps imports tidy.
/* index.css */
@layer base, components, utilities;
@import url("./css/base.css") layer(base);
@import url("./css/components/buttons.css") layer(components);
@import url("./css/components/cards.css") layer(components);
@import url("./css/utilities/spacing.css") layer(utilities);
Guardrails with linting and conventions
Automated checks catch drift. Use a linter to enforce patterns such as lowercase hex, no unused !important and consistent units. Add a short naming guide at the top of the repository so that new code follows the same rules.
/* Naming guide summary
Blocks: .card, .btn
Elements: .card__title
Modifiers: .btn--primary
States: .is-open, .is-invalid
Utilities: .u-*
*/
Prune unused rules
Old rules pile up over time. Periodically search templates for unused classes and remove them. A short checklist helps this become routine rather than a risky event.
/* Prune checklist
1. Search templates for class usage.
2. Remove rules with zero references.
3. Run visual tests.
4. Ship behind a feature flag when unsure.
*/
Document components where they live
Keep a small comment block above each component that explains purpose, public modifiers and any local tokens. This reduces ramp up time for new contributors.
/* Component: .badge
Purpose: small status label
Public modifiers: .badge--success, .badge--warning
Local tokens: --badge-bg, --badge-fg
*/
.badge { … }
.badge--success { … }
With clear names, layered components, shared tokens and a lightweight process for quality, your stylesheet remains calm as the project expands. The patterns in this chapter are small on purpose; they combine well and they are easy to teach to the next person who joins the work.
Chapter 18: Media Effects Without JavaScript
This chapter explores patterns that feel dynamic even though they do not rely on scripts. Simple selectors, careful layering and a few visual tricks can create moments of focus such as lightboxes, blurred backgrounds and soft image treatments. These patterns work smoothly on all modern browsers and keep the page light and easy to maintain.
Lightbox patterns with target
A lightbox effect draws attention to selected content while dimming or blurring everything behind it. One of the simplest ways to build this is the :target selector. When an element has an id and a link points to it, the browser marks that element as the active target. This creates a moment of focus without scripts.
Imagine a gallery where each thumbnail links to a modal panel. When the panel is the target, it appears above the page. The rest of the page can fade or blur which helps guide the eye gracefully. This works because the :target rule applies only while the link is active.
/* Modal panel */
.modal {
position: fixed; inset: 0;
display: none;
align-items: center; justify-content: center;
background: rgba(0, 0, 0, .4);
backdrop-filter: blur(4px);
}
/* Active when targeted */
.modal:target { display: flex; }
/* Inner box */
.modal__box {
background: #fff; padding: 1rem; border-radius: .5rem;
max-width: 400px;
}
The backdrop-filter property softens everything behind the modal. The effect feels natural because the viewer senses the page still exists under the blur. A close link simply points back to # or to a parent section which removes the target state.
Filters and subtle image effects
Filters can change tone without heavy editing tools. With filter you can soften shadows, tint images slightly or add a small atmospheric blur on hover. These effects work on any element including images, backgrounds and even text when used carefully.
.thumb { transition: filter .2s ease; }
.thumb:hover { filter: brightness(1.1) saturate(1.2); }
This gentle shift makes thumbnails feel a little more alive. Larger images can use a softer treatment such as blur(2px) that lifts focus away from the background section below them. Be sparing with blur because strong values can look heavy or artificial.
Refining the home video page
A home video page often mixes still images, short clips and links to deeper content. Lightbox patterns and subtle filters shape that mix without adding scripts. Thumbnails can brighten gently on hover; modal panels can show selected videos; and the page behind the modal can blur so that the viewer’s attention remains fixed on the media.
/* Video modal */
#video1:target + .modal-video {
display: flex;
}
.modal-video {
position: fixed; inset: 0;
display: none;
align-items: center; justify-content: center;
background: rgba(0, 0, 0, .4);
backdrop-filter: blur(5px);
}
.modal-video__frame {
width: 90vw; max-width: 640px;
border-radius: .5rem; overflow: hidden;
}
When used together, these small techniques create a calm, cinematic tone even though the page is built only with HTML and CSS. A clean structure, a few links and some careful layering are all that is required to make the page feel surprisingly polished.
Chapter 19: CSS Frameworks and What They Solve
Frameworks grew out of the simple wish to move faster. As sites became richer, developers found themselves repeating the same patterns again and again such as grids, buttons, form layouts and spacing rules. A framework gathers these patterns into a shared toolkit so that common problems can be solved with less typing. This convenience comes with tradeoffs because the toolkit must be updated, maintained and compiled in some cases. Even so, the appeal is strong because the code you write becomes smaller and more consistent.
Why frameworks exist
At their best frameworks save time. They offer a ready made grammar for layout and design so that teams can share the same vocabulary. Many of them also provide guardrails so that new pages keep a consistent tone. Some frameworks rely on multi pass build steps that compress styles, prune unused rules or create bundles. This keeps the final output small even though the source files are large. The tradeoff is that the build chain must stay healthy. When dependencies change or updates break patterns the framework may feel heavy instead of helpful.
Another reason frameworks thrive is that they flatten the early learning curve. Beginners can create tidy layouts with only a few utility classes. Seasoned developers may pair frameworks with custom layers that add branding or theme rules. In both cases the framework supplies a stable structure that guides the work.
A survey of current frameworks
Different frameworks have different strengths. Some provide full components; others focus on small utilities. Understanding these differences helps you choose wisely.
Bootstrap
Bootstrap is one of the oldest and most complete frameworks. It includes a grid system, buttons, forms, navigation patterns and many ready made components. The appeal is speed. You get a wide set of building blocks without designing them yourself. The tradeoff is weight because the base styles cover many patterns you may never use.
Tailwind
Tailwind focuses on tiny utility classes. You compose designs by stacking small helpers such as p-4, text-sm, bg-blue-500 and so on. A multi pass process removes unused utilities and compresses the final stylesheet. The appeal is control because every small detail is explicit. The cost is that your HTML may feel dense until you grow used to the pattern.
Bulma
Bulma offers a mixture of components and simple helpers with softer visual defaults. It aims for readability and keeps its class names friendly. No JavaScript is required which makes it attractive for static sites or content driven pages.
Material
Material frameworks follow the design language created by Google. They include components with animation, color guidance and strong visual rules. These frameworks help large teams create consistent interfaces that match platform expectations. The tradeoff is that the style has a strong personality which may not fit every brand.
Choosing tools in professional settings
Choosing a framework is partly a technical choice and partly a cultural one. A team must weigh speed, maintainability and long term fit. A utility first system may help a fast moving product team; a component heavy system may suit a site with stable patterns; a light framework may fit a content platform with simple layouts.
The key is to understand what problem you want the framework to solve. If your main concern is design consistency a component framework may serve you well. If your concern is flexibility and performance a utility approach might be better. If your concern is stability over many years a minimal set of custom components might be enough without a full framework.
Used with care, a framework becomes a steady partner that handles the small chores of layout and styling. Used blindly, it becomes an obstacle. The strongest teams treat frameworks as helpers that can be swapped or trimmed whenever the project evolves.
Chapter 20: Bringing It All Together
By the time you reach this chapter you have gathered a full toolkit. You know how to shape content with HTML; you know how to guide the eye with CSS; and you have seen how tiny rules can combine into designs that feel calm, clear and confident. This book was built to give you everything you need to begin making sites that reflect your own imagination. From here the path widens. You can experiment, stretch ideas and follow whatever sparks your curiosity because the fundamentals are now part of your natural rhythm.
A complete personal site
A personal site is a small world of your own making. It carries your voice, your taste and your sense of order. The chapters behind you showed how to build pages, craft layouts, reuse components and shape motion. When you assemble these pieces you can create a home page that feels consistent and welcoming. You can add a lightbox for photos, simple navigation for articles and a few thoughtful styles that tie everything together. The joy comes from shaping your own design story, one section at a time, with tools you now understand fully.
<header class="site-header"> … </header>
<main class="site-main"> … </main>
<footer class="site-footer"> … </footer>
As your site grows, you can decide what patterns deserve to be reused and what parts can stay simple. There is no single right layout. There is only the shape that best carries what you want to share.
Pathways to professional development
Professional work builds on the same ideas you used in earlier chapters. The difference is scale. Teams rely on structure, shared patterns and clear naming so that everyone can move together. You now know the building blocks that support that kind of work. You can read a component, understand a layout system, follow the cascade and contribute styles that fit naturally into a shared codebase.
Many workplaces mix custom components with selected framework pieces. Some lean heavily on tokens and layers. Others prefer simplicity. Whatever path a professional team takes, your understanding of semantic HTML, clean selectors, careful spacing and reusable patterns puts you in a strong position to step in and help with confidence.
Keeping your skills modern
The web shifts gently over time. New selectors appear; new layout techniques surface; old habits fade away. Staying modern does not mean chasing every change. It means staying curious. A small routine, such as reading release notes or exploring design showcases every few weeks, keeps your senses sharp. Trying one new pattern a month teaches you far more than trying ten in a single burst.
With practice you will notice that the fundamentals you learned here stay steady even while the language evolves. Good structure, clear logic and friendly design never go out of style. The details shift, but the craft stays the same. You now have the foundation to follow that craft boldly.
You are ready to explore. You can build pages that carry your taste, test new ideas and reshape patterns until they feel like your own work. This is the moment when the rules turn into intuition. Enjoy it. Let the browser be your canvas and let your curiosity guide what you create next.
© 2025 Robin Nixon. All rights reserved
No content may be re-used, sold, given away, or used for training AI without express permission
Questions? Feedback? Get in touch