HTML & CSS Fundamentals¶
The bar: you can hand-build a semantic, accessible, responsive multi-page site from a blank file — no framework, no template, no AI — and explain why every element and rule is there.
Everything a user sees on the web is HTML structure wearing CSS. Frameworks like React and Tailwind are conveniences stacked on top of these two languages; when a layout breaks in production, the fix lives down here, not in the framework. If you don't know what the browser is actually doing, you'll spend your career guessing.
So we start raw. No frameworks until you understand the machine underneath. This module takes you from an empty document to a styled, responsive page you built by hand — the muscle memory every shipping frontend engineer relies on without thinking about it.
4.1 HTML Document Structure¶
Objective: build properly structured HTML documents from the DOCTYPE down.
Every page opens with <!DOCTYPE html> and a root <html> element split into two halves. The <head> is metadata the user never sees directly — <meta> tags (charset, viewport, description), the <title>, <link> for stylesheets, and <script> for behavior. The <body> holds everything visible.
Structure the body with semantic elements — <header>, <nav>, <main>, <section>, <article>, <aside>, <footer> — instead of a pile of anonymous <div>s. Semantics give you a real document outline: screen readers navigate by it, search engines rank by it, and other engineers read it faster. The common self-taught mistake is "div soup," where nothing communicates meaning and accessibility is impossible to bolt on later.
Drill:
- Scaffold a valid page:
<!DOCTYPE html>,<html lang>,<head>,<body>. - Fill the head with
<meta charset>,<meta name="viewport">,<title>, and a<link rel="stylesheet">. - Lay out a page skeleton using
<header>,<nav>,<main>,<section>,<article>,<aside>,<footer>. - Run your markup through the W3C validator and read the document outline.
4.2 HTML Elements¶
Objective: use the right element for the job, semantically.
Headings <h1> through <h6> are hierarchy, not font size — one <h1> per page, and never skip levels to get a smaller heading. Text lives in <p>; use <span> for inline hooks and <div> for generic grouping only when no semantic element fits. Lists come in three flavors: <ul> unordered, <ol> ordered, and <dl> for term/definition pairs.
Links are <a href>, optionally with target and — critically — rel="noopener" when you open new tabs. Images need <img src> and a meaningful alt for accessibility, plus loading="lazy" for anything below the fold. Tabular data belongs in <table> with <thead>, <tbody>, <tr>, <th>, and <td> — never use tables for layout. Forms pull in <form>, <input>, <label>, <button>, <select>, and <textarea>.
Drill:
- Build a correct heading hierarchy
h1–h6with no skipped levels. - Write
ul,ol, anddllists. - Add links with
href,target, andrel; images withsrc,alt,loading. - Mark up a data table with
thead,tbody,th, andtd.
4.3 HTML Forms Deep Dive¶
Objective: build forms that are accessible and functional before any JavaScript touches them.
Pick the right <input type> — text, email, password, number, date, checkbox, radio — and the browser gives you correct keyboards, pickers, and validation for free. Every input needs a real <label> tied to it (wrap it or link for to id); a placeholder is a hint, not a label, and disappears the moment someone types. Group related fields with <fieldset> and <legend> so radio sets and sections announce themselves to assistive tech.
You get a validation layer with zero code: required, pattern, min, and max attributes let the browser block bad submissions. The form itself carries action (where data goes) and method (GET or POST). Nail this HTML layer first — JavaScript validation is an enhancement on top of it, never a replacement.
Drill:
- Use
type="email",type="number",type="date",checkbox, andradioinputs. - Pair every input with a
<label>; group with<fieldset>and<legend>. - Add native validation:
required,pattern,min,max. - Set
actionandmethodand submit to see the payload.
4.4 CSS Selectors¶
Objective: target exactly the elements you mean, and nothing else.
CSS starts with three base selectors — element (p), class (.btn), and ID (#header) — combined by relationship: descendant (nav a) reaches any depth, child (nav > a) only direct children, and attribute selectors (input[type="email"]) match on attributes. Pseudo-classes describe state or position: :hover, :focus, :active, :first-child, :nth-child. Pseudo-elements like ::before and ::after inject styleable content that isn't in the HTML.
The concept that trips everyone up is specificity — the scoring system browsers use to decide which rule wins when several match. IDs beat classes beat elements; that's why your rule "isn't working" even though it's clearly there. Combine specificity with the cascade and inheritance, and you can reason about any style conflict instead of reaching for !important.
Drill:
- Select by element,
.class, and#id. - Distinguish descendant (
nav a) from child (nav > a) combinators. - Match with attribute selectors and
:hover/:focus/:nth-child. - Add content with
::before/::after; resolve a real specificity conflict by hand.
4.5 The Box Model¶
Objective: see every element as a box made of four layers.
Every element is a rectangle wrapping four concentric layers: content, then padding, then border, then margin. Understand which layer you're adjusting and spacing stops being trial-and-error. Set box-sizing: border-box on everything early — with the default content-box, adding padding or a border grows the box past the width you set, which is the single most common "why is my layout wrong" bug.
Padding is space inside the border (background shows through); margin is space outside (transparent, and it separates elements). Reach for padding to make a box bigger inside, margin to push boxes apart. Watch for margin collapse: adjacent vertical margins merge into the larger of the two rather than adding. Live in the DevTools box-model diagram — it shows all four layers on any element you inspect.
Drill:
- Toggle
box-sizing: content-boxvsborder-boxand measure the difference. - Adjust
padding,border, andmarginindependently on one box. - Reproduce margin collapse between two stacked elements.
- Inspect the box-model visualization in DevTools.
4.6 CSS Layout Fundamentals¶
Objective: control where elements sit before you reach for Flexbox or Grid.
display sets the fundamental behavior: block stacks and takes full width, inline flows in text and ignores width/height, inline-block flows but accepts dimensions, and none removes the element entirely. position changes the coordinate system: static is default, relative nudges from the normal spot, absolute positions against the nearest positioned ancestor, fixed pins to the viewport, and sticky toggles between relative and fixed as you scroll.
Floats are legacy layout — you won't build with them anymore, but you must recognize them and clear in older code. When elements overlap, z-index decides the winner, but only within a stacking context, which is why a high z-index sometimes still loses. Finally, overflow (visible, hidden, scroll, auto) controls what happens when content exceeds its box.
Drill:
- Compare
block,inline,inline-block, andnone. - Position an element with each of
relative,absolute,fixed,sticky. - Read and clear a
float-based legacy layout. - Force and fix an overlap bug with
z-indexand a stacking context; contain scroll withoverflow.
4.7 Flexbox¶
Objective: build one-dimensional layouts — a row or a column — with Flexbox.
Set display: flex on a container and its children become flex items along a main axis, with a perpendicular cross axis. flex-direction chooses which is which (row or column). justify-content distributes items along the main axis (flex-start, center, space-between, space-around); align-items positions them on the cross axis (stretch, center, flex-start, flex-end). This pair handles the overwhelming majority of alignment you'll ever need.
Items flow onto one line unless you add flex-wrap. Each item's flexibility comes from flex-grow, flex-shrink, and flex-basis — how it expands, contracts, and starts. Use gap for spacing between items instead of margins; it's cleaner and doesn't leak to the edges. The classic confusion is mixing up the axes — remember justify-content follows the direction, align-items crosses it.
Drill:
- Switch
flex-directionbetweenrowandcolumnand watch the axes swap. - Center content with
justify-content: centerandalign-items: center. - Distribute with
space-between/space-around; wrap withflex-wrap. - Tune
flex-grow,flex-shrink,flex-basis; space items withgap.
4.8 CSS Grid¶
Objective: build true two-dimensional layouts — rows and columns together.
Where Flexbox handles a line, Grid handles a plane. display: grid plus grid-template-columns and grid-template-rows defines the tracks. The fr unit splits available space proportionally, and repeat() saves you typing repeat(3, 1fr) instead of 1fr 1fr 1fr. Space tracks with gap.
Place items explicitly with grid-column and grid-row, or name regions with grid-template-areas for layouts you can read like a map. For responsive grids that reflow without media queries, auto-fit and auto-fill with minmax() let columns wrap on their own. The rule of thumb: Flexbox for content in one direction (a nav bar, a button row), Grid when you're aligning in two directions at once (a page layout, a card gallery).
Drill:
- Define columns with
grid-template-columns,frunits, andrepeat(). - Space tracks with
gap; place items withgrid-column/grid-row. - Build a named layout with
grid-template-areas. - Make a self-reflowing grid with
auto-fit/auto-fillandminmax().
4.9 Responsive Design¶
Objective: build layouts that hold up on any screen from phone to desktop.
It starts with one line — <meta name="viewport" content="width=device-width, initial-scale=1"> — without which mobile browsers pretend to be desktop and zoom out. Media queries (@media (min-width: …) / max-width) let you change styles at breakpoints. Work mobile-first: write the small-screen layout as the base and layer complexity upward with min-width queries. It's less code and fewer overrides than desktop-first.
Prefer responsive units — %, vw, vh, rem, em — over fixed pixels so layouts scale with the viewport and the user's font settings. Make images responsive so they never overflow their container, and choose breakpoints based on where your content breaks, not on specific device widths. Then actually test across sizes in DevTools' device toolbar — a layout you never resized is a layout that's broken on somebody's screen.
Drill:
- Add the viewport
<meta>and confirm mobile scaling. - Write mobile-first
@media (min-width: …)breakpoints. - Size with
%,vw,vh,rem,eminstead of fixedpx. - Make images responsive and test every breakpoint in the device toolbar.
4.10 CSS Typography and Colors¶
Objective: style text and apply color with intent.
Type is set with font-family, font-size, and font-weight, tuned for readability with line-height and letter-spacing, and shaped with text-align, text-decoration, and text-transform. Good line-height (roughly 1.5 for body text) does more for readability than any font choice.
Colors come in hex, rgb, rgba (with alpha), and hsl — hsl is easiest to reason about when you want a lighter or more saturated variant of the same color. Backgrounds take colors and images. Define CSS custom properties (variables) like --brand: #1a2b3c and reference them with var(--brand) so your palette lives in one place instead of being copy-pasted everywhere. Load custom fonts with @font-face or a web-font link, and set a system-font fallback so text renders before the font arrives.
Drill:
- Control
font-family,font-size,font-weight,line-height,letter-spacing. - Use
text-align,text-decoration,text-transform. - Write colors as
hex,rgb,rgba, andhsl. - Define custom properties with
--var/var(); load a web font via@font-face.
4.11 CSS Transitions and Animations¶
Objective: add motion that guides attention without hurting performance.
A transition smooths a property change over time — transition: background-color 200ms ease turns an instant hover swap into a gentle fade. You control the property, duration, timing-function, and delay. This covers most interface motion: hover effects, color and size changes, focus states.
For multi-step or looping motion, define @keyframes and drive them with the animation shorthand (name, duration, iteration count). Animate with transform — translate, rotate, scale — and opacity whenever you can, because the browser handles those on the GPU without re-running layout. Animating properties like width, height, or top forces expensive reflows and makes motion stutter. Keep it subtle: animation should reinforce what happened, not show off.
Drill:
- Animate a hover with
transition(property, duration, timing-function, delay). - Write
@keyframesand run them via theanimationproperty with an iteration count. - Move things with
transform: translate/rotate/scale. - Compare animating
transform/opacityvswidth/topin DevTools performance.
4.12 Hands-On Lab: Static Website¶
Objective: build a complete, responsive, multi-page website with HTML and CSS only — every line by hand.
What you ship
A multi-page static site that proves you own the fundamentals, built with no frameworks and no AI tools:
- Semantic HTML structure across every page — real
<header>,<nav>,<main>,<footer>, correct heading hierarchy. - Hand-written CSS with no framework: your own selectors, box model, and custom properties.
- A responsive navigation bar that works on mobile and desktop.
- A responsive grid layout that reflows across breakpoints.
- A form with native validation and validation styling.
- Hover states and transitions that respond to interaction.
Every line written by hand. This is the site you can point to and say you understand exactly how it works, top to bottom.
On the VWC codebase
The Vets Who Code production app renders its UI with React and Tailwind CSS (note the tw- class prefix — unprefixed utility classes silently do nothing). Every Tailwind class you'll write there is a shorthand for the raw CSS in this module: tw-flex tw-justify-center is display: flex; justify-content: center, and tw-p-4 sets padding. When a component looks wrong in the real repo, you debug it in the browser's box-model and computed-styles panels — the exact skills you drilled here. The framework is a convenience; this is the machine underneath it.