Skip to content

CSS Essentials

SAP Implementation Consulting & ERP - Startupistan Germany · Block I · study notes for revision.


HTML builds the structure of a page - what each thing is. On its own it looks plain and unstyled, and that is HTML doing its one job correctly, not a sign something broke. CSS - Cascading Style Sheets - is the second language: it describes how that structure looks. Colours, fonts, sizes, spacing, layout - all of it lives in CSS.

Break the name in two:

  • Style Sheets - files full of styling rules, kept separate from the content they style.
  • Cascading - the way CSS settles arguments. Several rules can target the same element at once, and a fixed order decides which one wins.

One thing to fix early: CSS is not a programming language. There are no loops, no decisions, no logic - just declarations that say “this should look like that,” which the browser then applies. That is exactly why it is approachable from day one.


CSS can reach a page by three routes. I use one of them for real work, but I need to recognise all three because I’ll meet them in the wild.

RouteHow it attachesScopeUse it for
Inlinestyle attribute on one elementThat one elementHTML emails, quick tests, tool-generated markup
Internal<style> block inside <head>That one pageGenuine single-file pages, small demos
Externalseparate .css file linked from <head>Every page that links itEverything real - the default

The external route connects like this, with a link element in the head - rel names the relationship (a stylesheet), href holds the address:

<link rel="stylesheet" href="styles.css">

Why external wins, in three concrete reasons:

  • One file styles every page. Change a colour once and it changes everywhere. With internal styles, three pages means three copies quietly drifting apart.
  • Content files stay content. The HTML stays readable for what it is.
  • Browsers cache it. A returning visitor already has the stylesheet and doesn’t download it again, so pages load faster.

Everything in a stylesheet is built from one repeating shape. Its four parts have exact names, because documentation, DevTools, and every bug hunt use these words.

body { /* selector - which elements this targets */
background-color: #faf5ee; /* declaration: property + value, ends in ; */
color: #2f2a25; /* property = what aspect · value = the setting */
} /* the braces { } hold the declaration block */
  • The selector (body) says which elements the rule applies to.
  • The declaration block is everything between the braces.
  • Each declaration is one property: value; pair, ended by a semicolon.
  • The property names what aspect to style; the value sets it.

A selector plus its declaration block is a ruleset. A stylesheet is just rulesets stacked top to bottom.

The two silent breakages. CSS never reports errors to the page - it skips what it can’t parse and moves on. Silence is not correctness.

  • Missing semicolon - the parser runs the broken line into the next declaration and discards both. Symptom: one styling works, the next one mysteriously doesn’t. First thing to check: the semicolon on the line above.
  • Missing closing brace - breaks every ruleset after it in the file. Symptom: everything below a certain point dies at once. The editor’s bracket colouring flags it instantly, if I look.

The selector is where a ruleset takes aim. These five cover nearly everything, and one of them becomes the workhorse.

SelectorWritten asMatchesExample
Elementbare nameevery element of that kindh1 { color: #8a5a2e; }
Classleading dotevery element carrying that class.tagline { color: #6b6257; }
Idleading hashthe one element with that id (unique per page)#site-logo { ... }
Descendanttwo selectors, a space betweenelements inside another elementnav a { ... } (links inside the nav)
Pseudo-classcolon + statean element in a given statea:hover { color: #8a5a2e; }

Class is the workhorse. Any number of elements can share a class, and one element can carry several. I decide exactly which elements belong to a styling group, whatever kind they are. An id targets exactly one element forever - the rare, specific tool, wrong for anything that might ever have a sibling.

Two combining tricks earn their keep:

  • Grouping with commas applies one block to several selectors - h1, h2 { color: #8a5a2e; } reads “for every h1 and every h2.”
  • Descendant (a space) reaches inside - nav a styles only links inside the nav, leaving a link in a paragraph untouched.

CSS accepts a colour in several notations. Three matter now, one gets a mention:

NotationLooks likeNote
Namedgreen, white~140 names; handy in experiments, too coarse for real palettes
Hex#faf5eeThe workhorse. #RRGGBB - two hex digits each for red, green, blue, 00-FF. Every brand palette arrives this way
rgb()rgb(6, 48, 42)same three channels in decimal
hsl()hsl(174, 78%, 11%)hue, saturation, lightness; designers like it for reasoning about colour - recognise it

Two properties do the colouring: color sets the text colour, background-color sets the fill behind an element. Set both on body and the text colour inherits down to every paragraph, list, and heading until a more specific rule overrides it.

body {
background-color: #faf5ee; /* warm cream */
color: #2f2a25; /* dark warm brown, inherited everywhere */
}

Inspect & experiment Because CSS fails silently, the browser’s inspector is a beginner’s best friend, not an expert-only tool. Right-click any element on any page and choose Inspect. Chrome opens DevTools with the Elements panel (the live element tree) and the Styles pane beside it - showing every ruleset that applies, which file it came from, and its line number.

What it gives me:

  • Reading the cascade - rules are listed most-specific first; struck-through declarations lost the cascade, so “why isn’t my style applying?” becomes a reading exercise instead of a mystery.
  • Live editing - click any value and type a new one; the page updates instantly. Click a colour swatch and the picker even shows the contrast ratio against the background, with the 4.5:1 line marked.
  • Three diagnoses in one pane - a completely unstyled page means the stylesheet isn’t linked (usually a typo in the href); a ruleset that never appears means the selector matches nothing (a class typo); a warning icon marks a declaration a missing semicolon ate.

Most of the web is text, which makes typography the highest-leverage design skill here. It starts with two words people blur and professionals don’t:

  • A typeface is the design - the whole family of letterforms with a name (Playfair Display, Lato).
  • A font is one concrete style of that typeface - one weight, one posture (Playfair Display Bold; Lato Regular Italic).

The typeface is the family; each font is one member. CSS agrees - the property is font-family: I name the family, the browser picks the member each situation needs.

Two rules carry me far: legibility first for body text (it must disappear into effortless reading), character for the headings and brand (read in single lines, they can afford a distinctive voice); and two typefaces maximum - one with character for display, one calm workhorse for everything else. More than two reads as chaos.

Visitors don’t have my chosen typeface installed, and they don’t need to - the page can bring its own web fonts along. This course loads them from Google Fonts (free, open-license, copy-paste integration; alternatives worth knowing exist - Adobe Fonts, or self-hosting the files). Pick the typeface, select the weights needed (e.g. 400 regular and 700 bold), and paste the link elements into every page’s head, above my own stylesheet’s link:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">

Then name the family in CSS, always ending in a generic family as a safety net:

body {
font-family: "Lato", sans-serif; /* quote multi-word names; end with serif / sans-serif / monospace */
}

Readable pages aren’t luck - they’re a hierarchy: sizes, weights, and spacing chosen so a glance already says what’s a title, a section, and plain text. Five properties build one:

PropertySetsGood practice
font-sizehow large text renderspick from a small scale (e.g. body 1.6rem, h2 2.4rem, h1 3.6rem), not ad-hoc values
font-weightstroke heaviness400 (normal) and 700 (bold) - match the weights loaded from Google Fonts
font-styleupright or italicthe “costume” side of emphasis - looks only, no meaning
line-heightspace between lineswrite it unitless (1.6) so it scales with any font size; 1.5-1.6 for body
text-alignleft · center · rightlong text stays left; center is only for short display lines

The unit I choose for text is quietly an accessibility decision.

UnitTypeRelative toReach for it when
pxabsolutenothing - fixedborders, tiny fixed details; avoid for text size
remrelativethe root (html) font sizethe default for text & spacing - no compounding, scales with the visitor’s setting
emrelativethe current element’s font sizerarely - it compounds and gets hard to control
%relativethe parent’s valuefluid widths, and the 62.5% root trick
vw / vhrelative1% of viewport width / heightfull-screen heroes, viewport-sized blocks

Why rem is the accessible choice: every browser lets the user set a preferred default font size - a low-vision visitor might set 20px. Sizes in rem scale with that root, so the whole site respects the choice. Sizes in px ignore it flatly.


Every element the browser renders is a rectangle of four layers, from the inside out. This is the single most important mental model for spacing anything.

contentthe text or image itself
→
paddingspace inside, before the border
→
borderthe drawn edge
→
marginspace outside, to the next box
Content → padding → border → margin, from the inside out. Every element wears all four.
.card {
padding: 1.6rem; /* space inside the box */
border: 1px solid #e3d9c9; /* the drawn edge */
margin: 2rem; /* space outside, between neighbours */
}

One value sets all four sides; two values set vertical then horizontal (padding: 1.2rem 2rem; = top/bottom then left/right).

Before writing a single width, one question needs a fixed answer: when I say width: 300px, what exactly is 300px wide?

  • content-box (the browser default) - width measures the content only. Padding and border are added on top, so a 300px card with 20px padding and a 2px border actually renders 344px wide. Every padding change resizes the element, and layouts need arithmetic forever.
  • border-box - width measures the visible box (content + padding + border together). The same card renders exactly 300px; the content shrinks to make room inside. What I say is what I get.

The professional fix, one rule at the very top of the stylesheet using * (the universal selector, matching every element):

* {
box-sizing: border-box; /* width now means the visible box, everywhere - no arithmetic */
}

Sizing companions: width sets it, max-width caps it (the box stays fluid but never grows past the cap - the workhorse), and height mostly stays unset on text, because content should decide its own height. The classic centred container:

.container {
max-width: 96rem; /* cap the line length at a readable measure */
margin: 0 auto; /* auto left+right splits leftover space equally = centred */
padding: 0 2rem; /* keep content off the screen edges on narrow windows */
}

display sets which layout behaviour a box uses. The defaults I already know are its values:

ValueBehaviour
blockown line, full available width - headings, paragraphs, lists, div
inlineflows within text, takes only its own space, ignores width/height and vertical padding - a span can’t be sized like a box
inline-blockthe middle ground - flows in a line and accepts width, height, padding
noneremoved from the page entirely - takes no space at all
flexturns the element into a flex container - the door to layout

Flexbox arranges boxes along a line. Set display: flex on a container and its direct children become flex items that obey. Items line up along the main axis; the direction crossing it is the cross axis. Almost everything is set on the container - the items just follow.

  1. Make the container. display: flex on the parent. Its direct children are now flex items.

  2. Set the main axis with flex-direction - row (default, horizontal) or column (vertical). Every other property follows this choice.

  3. Place along the main axis with justify-content - flex-start (default), center, or space-between (first item at one end, last at the other).

  4. Align on the cross axis with align-items - center (the constant reach-for), flex-start, or stretch (default, equal heights).

  5. Space and wrap with gap (fixed space between items - no margin tricks) and flex-wrap: wrap (let a crowded row break onto a second line).

Here’s a full, copy-pasteable example - a header bar (brand pushed left, links pushed right) plus a wrapping row of cards:

<header class="header-bar">
<span class="brand">Café Aroma</span>
<nav>
<a href="#">Menu</a>
<a href="#">About</a>
<a href="#">Visit</a>
</nav>
</header>
<section class="card-row">
<article class="card">
<h3>Morning Bun</h3>
<p class="price">3.50</p>
</article>
<article class="card">
<h3>Rye Loaf</h3>
<p class="price">4.20</p>
</article>
<article class="card">
<h3>Almond Tart</h3>
<p class="price">3.90</p>
</article>
</section>
* { box-sizing: border-box; }
/* header: two ends pushed apart, everything on one centreline */
.header-bar {
display: flex;
justify-content: space-between; /* brand left, nav right */
align-items: center; /* same visual centreline */
padding: 1.2rem 2rem;
background-color: #2f2a25;
color: #faf5ee;
}
/* the nav is its own flex container for evenly spaced links */
nav {
display: flex;
gap: 2.4rem;
}
nav a { color: #faf5ee; text-decoration: none; }
/* card row: wraps to a new line when space runs out */
.card-row {
display: flex;
flex-wrap: wrap;
gap: 2.4rem;
}
.card {
background-color: #ffffff;
border: 1px solid #e3d9c9;
padding: 1.6rem;
max-width: 28rem;
}

Must-knowOne-line recall
CSSCascading Style Sheets - describes how HTML looks; not a programming language
Three ways to add itinline, internal, external - external wins (one file, clean content, cached)
Rulesetselector { property: value; } - a missing ; or } fails silently
Selectorselement / class (workhorse) / id / descendant (space) / pseudo-class; name classes by role
Colourhex #RRGGBB is the workhorse; color + background-color; every pairing ≥ 4.5:1 contrast
Typeface vs fonttypeface = the family; font = one weight/style of it → font-family, ending in a generic family
Web fontsload from Google Fonts into every page’s head; fallback list is the safety net
Type hierarchypick sizes from a scale; unitless line-height ~1.6; long text stays left-aligned
Unitsrem for text/spacing (accessible, scales); px fixed; the 62.5% root trick makes 1rem = 10px
Box modelcontent → padding → border → margin; background stops at the border, margin is transparent
box-sizing* { box-sizing: border-box; } at the top so width = the visible box
displayblock / inline / inline-block / none / flex (governs the children)
Flexboxdisplay:flex on the container; flex-direction · justify-content · align-items · gap · flex-wrap
DevToolsright-click → Inspect; read the cascade, experiment live, then copy winners into the stylesheet