Up to here, the Kladde’s prototype looks like a page from the early days of the web: black type, blue links, everything one below the other. That’s intentional, because the meaning came first. Now the looks are added, with CSS (Cascading Style Sheets). By the end of this lesson, the three pages share a stylesheet, they’re easy to read on the phone and on the screen, and they switch to a dark scheme with the device’s setting.
Important up front: Almost nothing changes in the HTML. A few classes are added so that the stylesheet hits the right elements. The meaning stays as it was.
How CSS gets to the page
A stylesheet is a file of its own. Every page links it in the head, after the title:
<link rel="stylesheet" href="css/kladde.css">
A stylesheet consists of rules. A rule has a selector that says which elements are meant, and in curly braces declarations,
each as property: value;:
/* Comments stand between slash-star and star-slash. */
h1 {
font-size: 2rem;
margin: 0 0 1rem;
}
A file of its own instead of settings directly in the HTML (style="…") has three advantages: All pages share the same rules, the
browser loads the file once and keeps it in its cache, and the HTML stays readable. That’s why there are no style attributes in the
Kladde.
Selectors
The most important selectors:
| Selector | hits | Example |
|---|---|---|
h1 |
all elements of this type | every main heading |
.card |
all elements with this class (class="card") |
every card |
#time |
the element with this id |
the time field |
[aria-current="page"] |
all elements with this attribute and value | the link of the current page |
.site-nav a |
all a anywhere inside an element with the class site-nav |
the navigation’s links |
.cards > .card |
.card that sit directly in .cards |
the home page’s cards |
h1, h2 |
both, a list | all main and section headings |
a:hover |
a state: the mouse pointer is on it | a link under the mouse |
:focus-visible |
a state: the element has the focus, and the browser considers a focus indicator necessary | a link you’ve jumped to with the Tab key |
For styling, use classes. They say what role an element has in the look and can be put on any number of elements. An id selector
always hits only a single element and is so strong that it’s hard to override (more on that in a moment). Class names are code, so
English and with hyphens: site-header, field-error.
Which rule wins: specificity
Often several rules hit the same element and set the same property. Then the selector’s specificity decides first. You count three columns:
- A: the number of
idselectors, - B: the number of classes, attribute selectors and states like
:hover, - C: the number of element types like
aornav.
The comparison goes from left to right, like with a date written year first, as in 2025-12-02: First A counts; only if A is equal does B count; then C.
| Selector | A, B, C |
|---|---|
a |
0, 0, 1 |
.site-nav a |
0, 1, 1 |
nav a[aria-current="page"] |
0, 1, 2 |
.site-nav .nav-link |
0, 2, 0 |
#main-menu a |
1, 0, 1 |
.site-nav .nav-link (0, 2, 0) beats nav a[aria-current="page"] (0, 1, 2), even though the second is longer: One more class weighs more
than any number of element types. And a single id beats everything without an id. That’s why you don’t use an id for styling:
Anyone who wants to change something later would otherwise have to counter with another id.
If the specificity is equal, the rule that comes later in the stylesheet wins.
There are two more things you should know but avoid: A style attribute in the HTML beats every selector, and !important after a
value turns the whole order upside down. Both solve one problem today and cause three new ones tomorrow.
The cascade and inheritance
The cascade is the whole order by which the browser decides which value applies. Simplified:
- Where does the rule come from? The browser has a stylesheet of its own with defaults (which is why links are blue and
h1is big). Your rules take precedence. - What specificity does the selector have?
- Which rule comes later?
Beside it there is inheritance: Some properties pass from the parent element to the children, above all those of the type: color,
font-family, font-size, line-height. That’s why you set type and color once on the body, and all the text takes them over.
Spacing, borders and backgrounds are not inherited; otherwise every paragraph in a card would have a border again.
Form fields are an exception with consequences: Browsers give input, select, textarea and button a font of their own instead of
inheriting it. With font: inherit; you bring it back.
The box model
Every element is a rectangle of four layers, from the inside out:
┌─────────────── margin (outer spacing, transparent) ───────────────┐
│ ┌─────────────── border (frame) ────────────────────────────────┐ │
│ │ ┌─────────────── padding (inner spacing) ──────────────────┐ │ │
│ │ │ │ │ │
│ │ │ content (text) │ │ │
│ │ │ │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
By the old default box-sizing: content-box, width means only the content. Padding and border are added:
.box {
width: 20rem; /* 320 px */
padding: 1rem; /* 16 px each on the left and right */
border: 2px solid; /* 2 px each on the left and right */
}
This box is 320 + 32 + 4 = 356 pixels wide, not 320. With box-sizing: border-box, width means the whole box up to and including the
border; it’s then exactly 320 pixels wide, and the content gets what’s left, here 284 pixels. That’s much easier to calculate. Almost
every stylesheet therefore begins with:
*,
*::before,
*::after {
box-sizing: border-box;
}
Two more quirks:
- Margins collapse. If two paragraphs stand one below the other, the upper one with a 16-pixel margin below and the lower one with 16 above, the space between them is 16, not 32. Only vertically, only with margins.
- Block and inline. Block elements (
p,div,section,h1) start a new line and take the full width. Inline elements (a,strong,span) flow along in the text; vertical spacing has hardly any effect on them. Withdisplay: inline-block, a link gets padding in all directions, so a bigger click target.
Units: why rem
pxis a fixed pixel (on high-resolution screens, several real dots).remis a multiple of the root element’s font size, usually 16 pixels. So1.5remis usually 24 pixels.emis a multiple of the element’s own font size; when nested it multiplies, which is rarely what you want.%refers to the parent element,vwto the width of the window.chis the width of the digit 0 in the current font, good for the length of lines.
Use rem for font sizes and spacing. Anyone who has set a larger base font in their browser because they have poor eyesight then gets a
larger page. With px for fonts, the page ignores that setting.
And limit the length of lines of text: From about 70 characters on, the eye has a hard time finding the start of the next line.
max-width: 68ch for paragraphs keeps lines readable, even on a wide screen.
Tokens: design values in one place
Colors and spacing appear dozens of times in a stylesheet. If #1f5f5b stands everywhere, changing a color is a search through the whole
file. Custom properties (often called “CSS variables”) solve that: You store a value once under a name and use it with var():
:root {
--color-text: #1d2422;
--color-surface: #fbfaf7;
--space-3: 1rem;
}
body {
color: var(--color-text);
background: var(--color-surface);
}
Such named design values are called tokens. Two rules for good names:
- Name by role, not by looks.
--color-dangerinstead of--color-red. In the dark scheme the error color is a lighter pink;--color-red: #ff8a8fwould then be a lie. - A small scale instead of arbitrary values. Five spacings (
--space-1to--space-5) are enough for a whole application, and everything looks like it’s from one mold.
Custom properties are inherited and apply at runtime. That sets them apart from the variables in SCSS, which you’ll meet in lesson 2.1.6 and which disappear at compile time. This very property is what the dark scheme needs.
A dark scheme on the same tokens
Many people set their device to a dark scheme. The page learns that through the media query prefers-color-scheme. Because all colors are
tokens, it’s enough to assign the tokens anew. Not a single other rule changes:
:root {
color-scheme: light dark;
--color-text: #1d2422;
--color-surface: #fbfaf7;
}
@media (prefers-color-scheme: dark) {
:root {
--color-text: #e6ebe9;
--color-surface: #141a19;
}
}
color-scheme: light dark tells the browser that the page knows both schemes. It then colors its own parts to match, such as the
scroll bars and the picker in a date field’s calendar.
In both schemes, watch the contrast between text and background. The rules for it and a small program that calculates it come in lesson 2.1.7. The colors in this lesson’s solution already meet them.
Flexbox: things in a row
Flexbox arranges an element’s children in a row (or column). The Kladde’s header: logo and navigation side by side, and the navigation’s links side by side too:
.site-header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-2) var(--space-4);
}
.site-nav ul {
display: flex;
flex-wrap: wrap;
gap: var(--space-1) var(--space-3);
margin: 0;
padding: 0;
list-style: none;
}
display: flexturns the children into flex items in one row.flex-wrap: wraplets them wrap when there isn’t enough room. Without this line they squeeze together or stick out past the edge, and on the phone a horizontal scroll appears.align-items: centeraligns them vertically in the middle.gapsets the spacing between them, first vertical, then horizontal. No margin needed on individual elements.justify-content: space-betweendistributes them across the whole width, the first at the far left, the last at the far right.
list-style: none takes the bullets off the list items, but not the meaning: For the screen reader it remains a list.
Grid: things in rows and columns
Grid arranges an element’s children in a grid. The home page’s two cards stand one below the other on the phone and side by side on the screen:
.cards {
display: grid;
gap: var(--space-3);
}
@media (min-width: 48rem) {
.cards {
grid-template-columns: repeat(2, 1fr);
}
}
grid-template-columns: repeat(2, 1fr) means: two columns that share the room equally (fr is a share of the free space). Without
this, a grid has a single column, and the cards stand one below the other.
As a rule of thumb: flexbox for a row whose elements are as wide as their content (navigation, buttons side by side), grid for a grid where the columns set the measure (cards, forms in columns, whole page regions).
Media queries: phone first
A media query applies rules only under a condition, such as from a minimum window width. You write the rules for the smallest
device first, without a media query, and then add for larger screens with min-width. That’s called mobile first.
h1 {
font-size: 2rem;
}
@media (min-width: 48rem) {
h1 {
font-size: 2.5rem;
}
}
Why in this direction? The simple layout, everything one below the other, is the one for the phone. It needs little CSS. For wide screens,
layout is added. The other way around, you’d have to take apart again for the phone what was built for the screen. The breakpoint in
rem (48rem is 768 pixels with the standard font) has an advantage: Anyone who has set a larger font gets the simple layout for longer,
and that’s right for them.
So that a page doesn’t slide sideways on the phone, three rules apply: the viewport entry from lesson 2.1.2, no fixed widths above
about 320 pixels, and images with max-width: 100%. Long words without a break point, such as email addresses in a table, may wrap with
overflow-wrap: anywhere.
The focus stays visible
Anyone working with the keyboard has to see where they are. Browsers draw a focus indicator for that, often thin and barely visible on some backgrounds. Strengthen it instead of removing it:
:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 2px;
}
:focus-visible applies when the browser considers the indicator necessary, so with the keyboard, not on every mouse click on a button.
Never write outline: none without an equivalent replacement. It’s one of the most common mistakes on websites at all, and lesson 2.1.7
checks exactly that.
Exercise 1: Who wins?
Given this HTML:
<nav class="site-nav" id="main-menu">
<a class="nav-link" href="index.html" aria-current="page">Home</a>
</nav>
and this stylesheet:
#main-menu a { color: orange; }
.site-nav .nav-link { color: purple; }
nav a[aria-current="page"] { color: red; }
.site-nav a { color: green; }
a { color: blue; }
- Determine the specificity of each of the five selectors as A, B, C.
- What color does the link have?
- What color does it have if you delete the first rule?
- And if you then delete the second one too?
- The last rule
a { color: blue; }stands at the very bottom, so the latest. Why does it never win anyway?
Check your answers in the browser: Put both in one file, open it and delete the rules one after the other.
Solution 1
#main-menu a: 1, 0, 1..site-nav .nav-link: 0, 2, 0.nav a[aria-current="page"]: 0, 1, 2..site-nav a: 0, 1, 1.a: 0, 0, 1.- Orange. Only this selector has an
id, and A is compared first. - Purple. Between 0, 2, 0 and 0, 1, 2, B decides, and 2 is more than 1. The two element types of the red selector count only when B is equal.
- Red. 0, 1, 2 against 0, 1, 1: A and B are equal, C decides.
- The order decides only with equal specificity. 0, 0, 1 is smaller than every other selector here.
Checked in the browser: With all five rules the link is orange, without the first purple, without the first two red.
Exercise 2: Calculating with the box model
The base font is 16 pixels. Calculate without a browser:
- How wide is an element with
width: 20rem; padding: 1rem; border: 2px solid;withbox-sizing: content-box? And withborder-box? How wide is its content then? - Two paragraphs stand one below the other. The upper one has
margin-bottom: 1.5rem, the lower onemargin-top: 1rem. How big is the space between them? - A person sets their browser’s base font to 20 pixels. How wide is the element from question 1 with
border-boxnow?
Solution 2
- With
content-box: 320 + 2 × 16 + 2 × 2 = 356 pixels. Withborder-box: exactly 320 pixels; the content is 320 − 32 − 4 = 284 pixels wide. - 24 pixels. Vertical margins collapse, and the larger one wins.
- 400 pixels, because 20rem is now 20 × 20. The border stays 2 pixels because it’s given in
px; the content is 400 − 40 − 4 = 356 pixels wide. That’s exactly as it should be: With the larger font the box grows along, and the text still fits in.
Exercise 3: Why isn’t the border red?
A stylesheet contains these two rules, in this order:
.field input {
border: 1px solid var(--color-muted);
}
[aria-invalid="true"] {
border: 2px solid var(--color-danger);
}
On the booking’s error page, the email field has aria-invalid="true", and it stands in a div with the class field. Yet its border
is gray and thin. Explain why, and change one selector so that the red border wins, without !important and without changing the
order of the rules.
Solution 3
.field input has the specificity 0, 1, 1, [aria-invalid="true"] only 0, 1, 0. An attribute selector counts like a class; the element
type input gives the first rule its lead in column C. That the second rule comes later doesn’t help it, because the order counts only
on a tie.
The solution is a second part in the selector:
.field [aria-invalid="true"] {
border: 2px solid var(--color-danger);
}
With 0, 2, 0 it now wins. input[aria-invalid="true"] (0, 1, 1) would work too, because on a tie it then comes later; but that no
longer hits a select. In lesson 2.1.5 you’ll find such cases in seconds with the browser’s tools: There the lost rule stands struck
through.
Exercise 4: Styling the prototype
Give the prototype’s three pages and the error page a shared stylesheet prototype/css/kladde.css. Proceed in this order and look in
the browser after each step:
- Classes in the HTML. In all four pages:
class="site-header"on theheader,class="site-nav"on thenav,class="site-footer"on thefooterand thelinkto the stylesheet in thehead. On the home page, the two sections stand assection class="card"in adiv class="cards". In the forms, thedivaround label and field gets the classfield, thedivaround radio buttons and checkboxes the classchoice, hints the classfield-hint, error messages the classfield-errorand the error list at the top the classerror-summary. - Tokens. Colors for text, muted text, background, cards, border, accent, text on the accent, links, focus and errors; spacing in five steps; a radius; the font; the line length.
- Basics.
box-sizing, font and colors on thebody, headings, line length of paragraphs, links, the focus indicator, images. - Page structure. Header with flexbox, navigation as a row, the main content centered with a maximum width, footer, cards with grid.
- Table and forms. Header cells and rows with lines; labels above the fields, fields at full width up to a maximum width, radio buttons beside their label, hints muted, errors in the error color with text and a thicker border; the button in the accent color.
- Wider screens. From
48rem: navigation on the right in the header, two cards side by side, a largerh1. - Dark scheme. All color tokens once more in
@media (prefers-color-scheme: dark).
The exercise is checked when all four points are met:
- At a window width of 360 pixels there is no horizontal scroll on any page. The easiest way is to set the width in the browser’s tools:
F12, then Ctrl+Shift+M (on the Mac Cmd+Shift+M) for the device emulation. More on this in lesson 2.1.5. As a test, type into the tools’
console
document.documentElement.scrollWidth <= document.documentElement.clientWidth; the answer must betrue. - At 1280 pixels, logo and navigation stand in one row and the two cards side by side.
- With the device’s dark scheme, or in the tools under “Rendering” with “Emulate CSS media feature prefers-color-scheme: dark”, every page is dark, without a rule other than the tokens appearing twice.
- The validator still reports nothing for the four pages, and the W3C CSS validator reports no
error for
kladde.css. You may ignore a warning that it can’t check custom properties.
Solution 4
The stylesheet:
/* kladde.css: the look of the prototype */
/* 1. Tokens: all design values in one place */
:root {
color-scheme: light dark;
--color-text: #1d2422;
--color-muted: #4a5552;
--color-surface: #fbfaf7;
--color-surface-raised: #ffffff;
--color-border: #c9cfcc;
--color-accent: #1f5f5b;
--color-on-accent: #ffffff;
--color-link: #1a5c8a;
--color-focus: #b35c00;
--color-danger: #a4262c;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 1rem;
--space-4: 1.5rem;
--space-5: 2.5rem;
--radius: 0.375rem;
--font-body: system-ui, "Segoe UI", Roboto, sans-serif;
--measure: 68ch;
}
@media (prefers-color-scheme: dark) {
:root {
--color-text: #e6ebe9;
--color-muted: #b4bfbb;
--color-surface: #141a19;
--color-surface-raised: #1d2524;
--color-border: #3d4a47;
--color-accent: #7cc4bd;
--color-on-accent: #0d1413;
--color-link: #8cc4f0;
--color-focus: #ffb366;
--color-danger: #ff8a8f;
}
}
/* 2. Basics */
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
margin: 0;
font-family: var(--font-body);
font-size: 1rem;
line-height: 1.5;
color: var(--color-text);
background: var(--color-surface);
}
h1,
h2 {
line-height: 1.2;
}
h1 {
font-size: 2rem;
margin: 0 0 var(--space-3);
}
h2 {
font-size: 1.375rem;
margin: var(--space-4) 0 var(--space-2);
}
p {
max-width: var(--measure);
}
a {
color: var(--color-link);
}
:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 2px;
}
img {
max-width: 100%;
height: auto;
}
/* 3. Page structure */
.site-header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: var(--space-2) var(--space-4);
padding: var(--space-3);
border-bottom: 1px solid var(--color-border);
}
.site-nav ul {
display: flex;
flex-wrap: wrap;
gap: var(--space-1) var(--space-3);
margin: 0;
padding: 0;
list-style: none;
}
.site-nav a {
display: inline-block;
padding: var(--space-2) var(--space-1);
}
.site-nav a[aria-current="page"] {
font-weight: 700;
text-decoration-thickness: 3px;
}
main {
max-width: 60rem;
margin: 0 auto;
padding: var(--space-4) var(--space-3) var(--space-5);
}
.site-footer {
padding: var(--space-3);
border-top: 1px solid var(--color-border);
color: var(--color-muted);
font-size: 0.875rem;
}
.site-footer p {
margin: 0;
}
.cards {
display: grid;
gap: var(--space-3);
}
.card {
padding: var(--space-3);
background: var(--color-surface-raised);
border: 1px solid var(--color-border);
border-radius: var(--radius);
}
.card h2 {
margin-top: 0;
}
/* 4. Tables */
table {
width: 100%;
border-collapse: collapse;
}
caption {
padding-bottom: var(--space-2);
font-weight: 700;
text-align: start;
}
th,
td {
padding: var(--space-2);
border-bottom: 1px solid var(--color-border);
text-align: start;
vertical-align: top;
}
thead th {
border-bottom-width: 2px;
}
td a {
overflow-wrap: anywhere;
}
/* 5. Forms */
fieldset {
margin: 0 0 var(--space-4);
padding: var(--space-3);
border: 1px solid var(--color-border);
border-radius: var(--radius);
}
legend {
padding: 0 var(--space-1);
font-weight: 700;
}
.field {
margin-bottom: var(--space-3);
}
.field label {
display: block;
margin-bottom: var(--space-1);
font-weight: 600;
}
.field input,
.field select,
.field textarea {
width: 100%;
max-width: 30rem;
padding: var(--space-2);
font: inherit;
color: inherit;
background: var(--color-surface-raised);
border: 1px solid var(--color-muted);
border-radius: var(--radius);
}
.field [aria-invalid="true"] {
border: 2px solid var(--color-danger);
}
.choice {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-2);
}
.choice input {
width: 1.25rem;
height: 1.25rem;
margin: 0;
accent-color: var(--color-accent);
}
.field-hint,
.field-error {
margin: var(--space-1) 0 0;
font-size: 0.875rem;
}
.field-hint {
color: var(--color-muted);
}
.field-error {
color: var(--color-danger);
font-weight: 700;
}
.error-summary {
margin-bottom: var(--space-4);
padding: var(--space-3);
border: 3px solid var(--color-danger);
border-radius: var(--radius);
}
.error-summary h2 {
margin-top: 0;
}
button {
padding: var(--space-2) var(--space-4);
font: inherit;
font-weight: 700;
color: var(--color-on-accent);
background: var(--color-accent);
border: 0;
border-radius: var(--radius);
cursor: pointer;
}
/* 6. Wider screens */
@media (min-width: 48rem) {
.site-header {
justify-content: space-between;
padding-inline: var(--space-4);
}
.cards {
grid-template-columns: repeat(2, 1fr);
}
h1 {
font-size: 2.5rem;
}
}
The home page with its classes:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Kladde</title>
<link rel="stylesheet" href="css/kladde.css">
</head>
<body>
<header class="site-header">
<a href="index.html"><img src="logo.svg" alt="Kladde, home page" width="48" height="48"></a>
<nav class="site-nav" aria-label="Main navigation">
<ul>
<li><a href="index.html" aria-current="page">Home</a></li>
<li><a href="contacts.html">Contacts</a></li>
<li><a href="appointment.html">Book an appointment</a></li>
</ul>
</nav>
</header>
<main>
<h1>Kladde</h1>
<p>The Kladde is the workbook of a small IT office: contacts, appointments and public tenders in one place.</p>
<div class="cards">
<section class="card">
<h2>What the Kladde can do</h2>
<ul>
<li>Keep contacts with company and email address</li>
<li>Plan appointments in the office</li>
<li>Book an appointment online without calling</li>
</ul>
</section>
<section class="card">
<h2>Office hours</h2>
<dl>
<dt>Monday to Thursday</dt>
<dd>9:00 to 17:00</dd>
<dt>Friday</dt>
<dd>9:00 to 13:00</dd>
</dl>
<p><a href="appointment.html">Book an appointment online</a></p>
</section>
</div>
</main>
<footer class="site-footer">
<p>Kladde, a practice project. All people and companies are made up.</p>
</footer>
</body>
</html>
In the forms, a field with a hint now looks like this, and so does every other field with its classes:
<div class="field">
<label for="email">Email</label>
<input type="email" id="email" name="email" autocomplete="email" required aria-describedby="email-hint">
<p class="field-hint" id="email-hint">We'll send the confirmation to this address.</p>
</div>
<div class="choice">
<input type="checkbox" id="reminder" name="reminder" value="yes">
<label for="reminder">Remind me by email the day before the appointment</label>
</div>
On the error page, the list at the top gets class="error-summary" and each message at the field class="field-error":
<div class="error-summary">
<h2>Please check two details</h2>
<ul>
<li><a href="#time">Choose a time.</a></li>
<li><a href="#email">The email address needs an @, for example name@example.com.</a></li>
</ul>
</div>
On the phone, the navigation stands below the logo, the cards stand one below the other, and the email addresses of the contact list wrap in their column instead of widening the page. On the screen, the navigation stands on the right, the cards side by side, and the paragraphs still stay narrow enough to read. Your colors may be different; but keep the names of the tokens by role, and have the contrasts calculated in lesson 2.1.7.
Three places where it often snags:
- The type in the fields is smaller than the text.
font: inheritis missing on the fields and the button. - Radio buttons are huge in width. The rule for fields also hits
input type="radio"if it’s written asinputinstead of.field input. That’s why radio buttons stand in.choiceand not in.field. - Something stays light in the dark scheme. Somewhere a color stands directly in the stylesheet instead of as a token. Search for
#outside:root.
The Kladde: the styled prototype
On a branch of their own, commit the stylesheet and the four changed pages, with a message like “Style the prototype for phone and desktop, with a dark scheme”, and open a pull request. Attach two screenshots to the description, one at 360 and one at 1280 pixels, each light and dark. In a review of CSS, a picture says more than any explanation.
In lesson 2.1.6 you’ll break this one stylesheet into smaller SCSS files. Afterward, the page has to look exactly the same; so keep the screenshots.
What you can do now
- You link a stylesheet and select elements with type, class and attribute selectors, with combinators and states.
- You determine a selector’s specificity and predict which rule wins; you style with classes, not with
id. - You explain the cascade and inheritance and bring the font back to form fields with
font: inherit. - You calculate with the box model, set
box-sizing: border-boxand useremfor fonts and spacing. - You lay out with flexbox and grid, phone first, and extend with
min-widthfor wide screens. - You keep colors and spacing as tokens and build the dark scheme through the tokens alone.
Read more
- Build web pages with HTML and CSS for beginners: the Microsoft Learn learning path, with a module on CSS.
- Inspect and debug CSS grid in Microsoft Edge DevTools: how the tools make a grid visible; you need this in lesson 2.1.5.
- CSS at MDN: reference pages for every property, with notes on browser support.
- Specificity at MDN: the rules of specificity in detail,
including for selectors like
:is()and:where().