Keentune
CSS, oriented
11 chapters
·
about 17 min read
·
free
CSS looks like a pile of properties to memorise. It is really four systems running at once: a cascade that decides which declaration wins, a box model that decides how big a box is, a layout mode that decides where boxes go, and a containing block that every percentage and every positioned element resolves against. Nearly every "why is it doing that?" moment is one system being asked a question another one answers. This guide is the shape of the four, and the traps at their seams.
Each chapter opens with the short version. Tap one to read the detail.
How a stylesheet is read, and which rule wins
~3 min
The browser resolves each property separately: origin and !important, then cascade layer, then specificity, then source order. Selectors only reach downward and forward, and specificity is three columns compared left to right — so a hundred classes never beat one id, and :where() costs nothing at all.
The box model, and what width actually sizes
~2 min
By default width sizes only the content box, so padding and border are added outside it; box-sizing: border-box folds them inside. Vertical margins between blocks collapse to the larger one rather than adding, and percentage padding resolves against the container's width even on the top and bottom.
Values, units and colour
~2 min
em resolves against the element's own font size and therefore compounds through nesting; rem resolves against the root and never does. A unitless line-height inherits as a ratio, while 1.5em inherits an already-computed length — the most common typography bug in the language.
Normal flow, and the formatting context you cannot see
~2 min
Before any layout mode is chosen, boxes are in normal flow: blocks stack down, inline boxes run along the line and ignore width, height and vertical margins entirely. A block formatting context is the invisible boundary that contains floats and stops margins escaping.
Flexbox: distributing space along one axis
~2 min
flex-direction picks the main axis and the cross axis follows, which is why justify-content starts moving items vertically in a column. flex: 1 means a basis of zero, so items come out equal whatever their content — and no item shrinks below its own content unless you set min-width: 0.
Grid: define the tracks, then place on the lines
~2 min
You define tracks, but you place items on the numbered lines between them, so grid-column: 1 / 3 spans two columns rather than three. fr shares out whatever is left after gaps and fixed tracks, and a 1fr track will not shrink below its content unless you write minmax(0, 1fr).
Positioning, stacking contexts, and why z-index stops working
~2 min
An absolutely positioned box resolves against its nearest positioned ancestor, and a transform or filter anywhere above it captures even position: fixed. z-index is only ever compared inside the nearest stacking context, and opacity below 1 quietly creates one.
Shorthands, and the paint and text they quietly reset
~3 min
A shorthand resets every longhand it omits: background silently clears background-color, and font clears the line-height you set two lines earlier. Multiple backgrounds stack with the first entry on top, and text-transform changes only the rendering — the copied text keeps its original casing.
Transforms and motion
~2 min
A transform changes what is painted, never the layout — the element keeps occupying its untransformed space. Functions compose left to right, each in the coordinate space the previous one left behind, so translate then rotate lands somewhere quite different from rotate then translate.
Media queries, and querying the container instead
~2 min
A media query tests the viewport, and min-width: 600px matches at exactly 600px, so a naive max-width: 600px / min-width: 600px pair overlaps by one pixel. Container queries ask the same questions of an ancestor's size, which is what component styling actually needs.
Custom properties, layers, and the cascade tools worth adopting
~2 min
Custom properties are live cascading values, not compile-time substitutions: they inherit, and redefining one on a subtree changes everything reading it below. Their sharp edge is that an unresolvable var() does not fall back to your previous declaration — it makes the property unset.
See the full CSS curriculum
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC