Keentune

CSS curriculum

20 chapters
·
225 concepts
·
free
Everything the adaptive question bank can teach and test in CSS, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the CSS guide
A free 17-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. How CSS works — syntax, parsing and at-rules
a selector plus a {} block of property: value; declarations
the parser discards a malformed declaration and keeps the rest of the block
a shorthand sets every longhand it covers, omitted ones back to their initial value
!important lifts one declaration above all normal declarations in its origin
only /* */; CSS has no line-comment form
statement at-rules end at a ;, block at-rules carry a {} body
@import is only valid before other rules, and it serializes the download
--X and --x are different properties; COLOR and color are not
declared → cascaded → specified → computed → used → actual value
initial, inherit, unset, revert, revert-layer are accepted by every property
a prefixed property is a separate property, and an unknown one is dropped harmlessly
B. Cascade, specificity and inheritance
for normal declarations, author styles beat user styles beat the user-agent default
with !important that order flips, so a user's important beats the author's
origin and importance first, then cascade layer, then specificity, then source order
count IDs, then classes/attributes/pseudo-classes, then types/pseudo-elements
a higher column always wins; a hundred classes never reach one ID
a style attribute outranks any selector, but not an !important rule
at equal specificity the later declaration wins
*, combinators and :where() contribute nothing to specificity
:is() and :not() take the specificity of their most specific argument
color, font, line-height, visibility inherit; margin, border, background do not
inherit takes the parent's computed value, initial the property's spec default
unset is inherit-or-initial; revert rolls back to the previous origin's value
the cascade runs property by property, so two rules can each win something
all: revert resets every property except direction and unicode-bidi
between cascade layers the later layer wins whatever the specificity
browser stylesheets supply display, margins and link colors, which is what resets undo
C. Selectors and combinators
type, class, id and *, and what each one costs in specificity
[attr] tests presence, [attr="v"] tests an exact value
^= prefix, $= suffix, *= substring, ~= whitespace-word, |= prefix-or-hyphen
a trailing i makes the attribute value match case-insensitively
a space matches any depth, > only a direct child
+ matches the immediately next sibling, ~ any later sibling
selectors match forward and downward only, which is why :has() was needed
:hover, :active, :focus, and :focus-visible vs :focus-within
an+b counts from 1, so 2n is even, 2n+1 odd, -n+3 the first three
:nth-child counts every sibling, :nth-of-type only same-tag siblings
:first-child, :last-child, :only-child, and :empty (text and whitespace count as content)
:is() groups, :where() groups at zero specificity, :not() excludes
one invalid selector voids a plain A, B list but not :is(A, B)
:has() styles an element from its descendants or later siblings (baseline 2023)
::before/::after need a content value and render inside the element's box
::first-line, ::first-letter, ::selection, ::placeholder, ::marker
:root, :target, :lang(), :dir()
:checked, :disabled, :required, :valid/:invalid, :in-range
they have no DOM node for JS to select, and replaced elements have none at all
D. The box model
content, padding, border and margin nest outward from the content edge
by default width sizes the content box, so padding and border add to it
box-sizing: border-box folds padding and border inside the declared width
under content-box, outer width = width + horizontal padding + borders + margins
adjacent vertical margins merge into the larger one, never the sum
a parent collapses with its first/last child unless padding, a border or a BFC intervenes
horizontal margins never collapse, and neither do flex or grid item margins
with a negative in the mix the result is largest positive plus most negative
a block fills the container's width but its height shrinks to content
percentage padding and margin resolve against the container's WIDTH, even vertically
margin: 0 auto centers a block only when it has a definite width
a negative margin pulls the box and can shrink its own width or overlap a neighbor
outline paints outside the border and occupies no layout space
E. Values and units
px is the CSS reference pixel; pt, in, cm are fixed ratios of it, not device units
em resolves against the element's own font-size, so nesting compounds it
rem resolves against the root font-size and never compounds
used ON font-size, em refers to the PARENT's size
% picks a different base per property: width→container width, line-height→font-size
a unitless line-height inherits the factor; 1.5em inherits an already-computed length
vw/vh/vmin/vmax are 1% of the viewport and ignore the scrollbar
svh, lvh and dvh model the collapsing mobile toolbar
ch is the 0 advance width, ex the x-height, lh one line box
calc() mixes units, but + and - require whitespace on both sides
min() returns the smallest argument and max() the largest, resolved at used-value time
clamp(a, b, c) is exactly max(a, min(b, c)), which is how fluid type is built
angles (deg/rad/turn), time (s/ms), resolution (dpi/dppx)
only 0 may drop its unit; opacity, z-index, flex-grow take bare numbers
F. Display and normal flow
block takes its own line, inline flows within text, inline-block does both
width, height and vertical margins have no effect on a non-replaced inline box
display: none removes the box, visibility: hidden keeps its space
display: inline flex names the outer and inner display types separately
normal flow stacks in the block direction and lays out along the inline direction
a Block Formatting Context is an isolated layout region: it contains floats and stops collapse
non-visible overflow, display: flow-root, floats, absolutes, and flex/grid items each make one
display: contents removes the element's own box but keeps its children in flow
mixed inline and block children force boxes the author never wrote
img, video, input have intrinsic size and generally take no generated content
G. Flexbox
display: flex makes only the DIRECT children flex items
flex-direction picks the main axis; the cross axis is perpendicular to it
justify-content works along the main axis, align-items along the cross axis
under flex-direction: column, justify-content moves items vertically
leftover free space is shared out in proportion to each item's flex-grow
final size = basis + free space × (this grow ÷ the sum of all grows)
overflow is removed in proportion to shrink factor × base size, not shrink alone
flex-basis sizes the item on the main axis and takes precedence over width
flex: 1 means 1 1 0%, flex: auto means 1 1 auto, a bare number sets grow
items stay on one line until flex-wrap: wrap lets the container add lines
aligns the LINES, so it has no effect on a single-line flex container
overrides align-items for one item without touching the others
order changes paint and layout order but not the DOM or tab order
gap spaces items without the last-child margin hack
an auto margin on a flex item eats all free space on that side
align-items: stretch makes items match the tallest unless a cross size is set
a flex item's min-width: auto stops it shrinking below its content
H. Grid
grid-template-columns/-rows define the explicit tracks and the lines between them
fr distributes the space left after fixed tracks and gaps are subtracted
1fr is minmax(auto, 1fr), so wide content stops the track shrinking
repeat(3, 1fr) and repeat(2, 100px 1fr) expand into a longer track list
subtract gaps and fixed tracks from the container, then split the remainder by fr share
minmax(min, max) gives a track both a floor and a ceiling
auto-fill keeps empty tracks, auto-fit collapses them to zero width
grid-column: 1 / 3 names the start and end LINES, not the columns
span 2 states how many tracks to cover without naming the far line
-1 is the last explicit line, so 1 / -1 spans the whole row
grid-template-areas draws the layout as text and names each region
items placed outside the explicit grid create tracks sized by grid-auto-rows/-columns
grid-auto-flow fills by row or column, and dense backfills holes out of order
gap adds space between tracks and is subtracted before fr is computed
justify-items/align-items position items inside their area; -self overrides per item
justify-content/align-content move the whole set of tracks inside the container
place-items and place-content take block alignment then inline
an auto track sizes to its content, an fr track claims free space
subgrid makes a nested grid adopt the parent's track lines (baseline 2023)
flex lays out one axis with content-driven wrapping, grid places items on two axes you define
I. Positioning and stacking
a static box ignores top/left and z-index entirely
relative shifts where the box paints but reserves its original slot
an absolute box is removed from flow and its siblings close the gap
an absolute box resolves against the nearest non-static ancestor, else the initial containing block
with no inset values an absolute box stays where it would otherwise have been
fixed positions against the viewport and does not move when the page scrolls
a transform, filter or will-change ancestor becomes the containing block for fixed
sticky behaves as relative until its threshold is crossed, then sticks within its scroll container
sticky needs an inset value and a scrolling ancestor; an overflow: hidden ancestor kills it
top/right/bottom/left and the inset shorthand apply only to positioned boxes
z-index works on positioned boxes and on flex and grid children
opacity below 1, transforms, filters, will-change and a positioned z-index each create one
a child's z-index only competes inside its parent's stacking context
J. Backgrounds, borders and shadows
the background shorthand resets every longhand it omits, including background-color
comma-separated backgrounds paint the first on top, and only the last layer may set a color
cover fills the box and crops, contain fits the image and leaves gaps
a position percentage aligns that point of the IMAGE with the same point of the box
fixed paints against the viewport, local scrolls with the element's own content
background-clip limits where the painting shows, background-origin where it starts
in linear-gradient, 0deg points up, and stops interpolate between the positions you give
a / separates horizontal from vertical radii, and percentages produce ellipses
offset-x, offset-y, blur, spread, color, plus inset; the first shadow in the list paints on top
an outline takes no layout space, can be offset, and may follow the border shape
K. Typography and text
the font shorthand requires size and family and resets everything else, line-height included
the family list is tried left to right, and should end in a generic family
numeric weights run 100–900, while bolder/lighter are relative to the inherited weight
@font-face declares a face; font-display: swap trades an invisible flash for an unstyled one
letter-spacing, word-spacing, text-indent and text-align including justify
it changes rendering only; the DOM text and the copied text are unchanged
nowrap stops wrapping, pre preserves whitespace and newlines, pre-wrap does both
needs overflow hidden, a non-wrapping line and a constrained width to appear
-webkit-line-clamp truncates after N lines and still requires display: -webkit-box
vertical-rl rotates the block axis, which changes what width and height mean
L. Color
#rgb expands by doubling each digit, and #rrggbbaa adds an alpha byte
the modern space-separated syntax puts alpha after a /, and HSL rotates hue in degrees
lab(), lch() and oklch() address colors beyond sRGB, and oklch keeps lightness perceptual
a translucent color affects one property, while opacity fades the element and every descendant
currentColor resolves to the element's computed color, which is how borders and SVG track it
color-mix(in oklch, a 30%, b) blends two colors in a named space (baseline 2023)
transparent is rgb(0 0 0 / 0), and named colors are a fixed sRGB list
M. Transforms
translate(), scale(), rotate() and skew() in two dimensions
functions apply left to right in the element's own moving coordinate system
a translate percentage is of the ELEMENT's own size, not the container's
the pivot defaults to the box's center, which is what rotation and scaling turn around
translate, rotate and scale are standalone properties applied before transform
perspective, rotateX/Y/Z and transform-style: preserve-3d build a 3D scene
a transformed box still occupies its untransformed space, so siblings do not move
it promotes an element to its own layer, and overusing it costs memory and can slow paint
N. Transitions and animations
property, duration, timing function, delay — and with no duration nothing animates
only interpolatable properties transition; display and content historically cannot
ease is the default, cubic-bezier() shapes the curve, steps() jumps
@keyframes defines states with from/to or percentage stops
name, duration, timing, delay, count, direction, fill mode and play state in one value
forwards holds the final frame, backwards applies the first frame during the delay
infinite with alternate reverses every second pass
animating transform and opacity skips layout and paint
@starting-style plus transition-behavior: allow-discrete animates out of display: none
prefers-reduced-motion: reduce should shorten or remove non-essential motion
O. Responsive design and container queries
a media type plus feature tests joined by and/not, with commas acting as OR
(400px <= width <= 700px) replaces paired min-/max- features
min-width: 600px matches AT 600px, so a naive max/min pair overlaps
min-width queries layer up from the base styles, max-width queries layer down
without width=device-width a phone lays out at roughly 980px and scales the result down
the dark-mode query, and why it pairs with the color-scheme property
hover, any-hover, pointer and prefers-contrast describe the device, not the width
@container tests an ancestor container's size instead of the viewport (baseline 2023)
container-type: inline-size makes an element queryable and applies size containment to it
cqw, cqi and cqb resolve against the nearest query container
P. Overflow and scrolling
visible, hidden, clip, scroll and auto, and how clip differs by forbidding scrolling
setting one axis to a non-visible value makes visible on the other behave as auto
scroll-behavior: smooth animates anchor and programmatic scrolling
scroll-snap-type goes on the scroll container, scroll-snap-align on the children
contain stops a panel's scroll from chaining to the page behind it
reserving the gutter prevents the layout shift when a scrollbar appears
Q. Sizing and intrinsic sizes
max-width overrides width, and min-width in turn overrides max-width
min-content is the widest unbreakable piece, max-content the fully unwrapped width
fit-content is max-content clamped by the available space, and fit-content() adds an explicit cap
a % height falls back to auto unless the parent's height is definite
aspect-ratio derives the missing dimension from the one that is set
100vw includes the classic scrollbar width and 100% does not, which is the horizontal-scroll bug
R. Modern layer — custom properties, cascade layers, feature queries, nesting
--x: value is declared on a selector and read back with var(--x)
they cascade and inherit at runtime, unlike a preprocessor variable
in var(--x, 1rem) everything after the first comma is the fallback, commas included
an unresolvable var() makes the property unset rather than ignoring the declaration
@property gives a custom property a syntax, an initial value and inheritance, which makes it animatable
@layer a, b; fixes the order up front, whatever order the blocks arrive in later
unlayered author styles beat every layered one, and that order inverts under !important
@supports (property: value) and @supports selector(...) gate progressive enhancement
a nested rule implies a descendant relationship; & places the parent selector explicitly
a nested selector takes :is()-style specificity from the whole parent selector list
margin-inline, padding-block and inset-inline-start follow writing mode, not the screen
color-scheme opts an element into native dark form controls and scrollbars
S. Visual effects
blur(), brightness(), grayscale() and drop-shadow(), which follows alpha rather than the box
it filters what is BEHIND the element, so the element itself needs some transparency
mix-blend-mode blends with the backdrop, background-blend-mode between one element's own layers
inset(), circle() and polygon() clip the painted area without changing layout
mask-image reveals an element through the alpha (or luminance) of another image
opacity below 1 creates a stacking context and composites the element with its children as one group
T. Lists, tables, counters and generated content
list-style-type, list-style-position, and styling the bullet via ::marker
list-style: none can drop list semantics in some screen readers, so restore role="list"
fixed sizes columns from the first row and is far cheaper than auto, which reads the whole table
collapse merges adjacent borders and resolves conflicts by width, then style
counter-reset, counter-increment and counter() used inside content
content accepts strings, attr(), counters, images, and alt text after a /
pointer-events: none passes clicks through, user-select: none blocks selection
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about CSS practice
Also on your phone
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