Cheat SheetsHTML, CSS & ResponsiveCSS Fundamentals

CSS Fundamentals — Cheat Sheet

HTML, CSS & Responsive · 6 topics. Download the PDF or the Instagram carousel and share it.

Cheat Sheet · AiCanCode.org
CSS Fundamentals
HTML, CSS & Responsive6 topicsQuick revision reference
1

The Cascade, Specificity and Inheritance

When two rules apply, one wins. Knowing exactly how that is decided is the difference between debugging CSS and adding !important until it works.

  • The cascade resolves in order: origin and importance, then specificity, then source order
  • Specificity is compared column by column — one id beats any number of classes
  • :where() has zero specificity and @layer sets priority explicitly, both of which prevent override wars
  • Text properties inherit; box properties do not — and form controls need font: inherit explicitly
  • The DevTools Styles panel shows the winner at the top with losers struck through, which answers most CSS questions instantly
Origin, then specificity, then order
/* 1. ORIGIN and IMPORTANCE
      author !important > author > user agent (browser defaults)

   2. SPECIFICITY  — counted as (inline, id, class, element)

   3. SOURCE ORDER — the last matching rule wins  */

#main .card p        /* (0,1,1,1) */
.card p.intro        /* (0,0,2,1) */
p                    /* (0,0,0,1) */
.btn                 /* (0,0,1,0) */
style="color:red"    /* (1,0,0,0) — beats every selector */

/* Compare left to right. A single id beats any number of classes:
   (0,1,0,0) > (0,0,20,0) — specificity does not "add up" across
   columns, which surprises people. */

/* Pseudo-classes count as classes; pseudo-elements as elements */
a:hover              /* (0,0,1,1) */
li::marker           /* (0,0,0,2) */

/* These three add nothing at all */
:where(.a, .b)       /* (0,0,0,0) — always zero */
*                    /* (0,0,0,0) */
:is(#x)              /* takes the HIGHEST of its arguments — careful */
2

The Box Model and Spacing

Every element is a box with content, padding, border and margin. Two rules — border-box sizing and single-direction margins — remove most sizing surprises.

  • box-sizing: border-box makes width include padding and border, which is what people expect — set it globally
  • Adjacent vertical margins collapse to the larger value, and a child's margin can escape its parent
  • Flex and grid gap never collapses, which is why gap-based spacing avoids the whole problem
  • Prefer constraints — max-width, min(), aspect-ratio — over fixed pixel sizes
  • overflow: hidden creates a formatting context and silently breaks position: sticky inside it
Set border-box globally, once
/* Default: content-box. width applies to the CONTENT only. */
.card { width: 300px; padding: 20px; border: 2px solid }
/* Rendered width = 300 + 40 + 4 = 344px. */

/* border-box: width includes padding and border. */
*, *::before, *::after { box-sizing: border-box }
.card { width: 300px; padding: 20px; border: 2px solid }
/* Rendered width = 300px, with 256px of content. */

/* This is why 'width: 50%' with padding overflows its parent under
   the default and behaves under border-box. Set it once, globally,
   and never think about it again. */

/* The four layers, outside in:
     margin   — space OUTSIDE, transparent, can collapse, not in width
     border   — the visible edge
     padding  — space INSIDE, takes the background
     content  — the text or child boxes                          */

/* Padding vs margin: background reaches into padding, not margin */
.button { padding: .5rem 1rem }     /* clickable, coloured */
.button { margin: .5rem 1rem }      /* space around, not clickable */
3

Selectors and Pseudo-Classes

Beyond classes there is a whole vocabulary — combinators, structural pseudo-classes, :has() — that removes the need for extra markup and JavaScript.

  • Combinators and nth-child style position without adding classes; > * + * is the standard spacing idiom
  • Attribute selectors can style from ARIA state, keeping CSS and accessibility in sync
  • :focus-visible targets keyboard focus only, which is why it replaced the outline-removal habit
  • :has() selects a parent by its contents and removes a surprising amount of JavaScript
  • Generated content from ::before is decorative — it is not reliably announced, selectable or translatable
Combinators, nth-child, attributes
.card .title      /* descendant, at any depth */
.card > .title    /* direct child only */
.a + .b           /* the next sibling, immediately after */
.a ~ .b           /* any following sibling */

/* Structural — no extra classes needed */
li:first-child    li:last-child    li:only-child
li:nth-child(2)   li:nth-child(odd)   li:nth-child(3n)
li:nth-last-child(2)
p:first-of-type   /* first p, regardless of other siblings */
:empty            /* no children at all, not even text */

/* Two very useful idioms */
.stack > * + *          { margin-block-start: 1rem }  /* space between */
tr:nth-child(even)      { background: var(--surface) } /* zebra stripes */

/* Attribute selectors */
a[href^="http"]         /* starts with */
a[href$=".pdf"]         /* ends with */
[data-state="open"]     /* exact */
[aria-invalid="true"]   /* style from ARIA state — keeps CSS and
                           accessibility in sync automatically */

/* :is() and :where() shorten long lists */
:is(h1, h2, h3) + p { margin-block-start: .5rem }
4

Units — px, rem, em, %, and the Viewport

The unit you choose decides whether your layout respects a user who has increased their browser font size. rem for type and spacing, px for hairlines, and the viewport units that actually work on phones.

  • rem scales with the user's browser font setting, so type and spacing in px ignore an accessibility preference
  • The html { font-size: 62.5% } trick halves the text for anyone who changed their default — avoid it
  • em is relative to the current element and compounds through nesting
  • 100vh on phones is the large viewport and hides content under the address bar; dvh follows the chrome
  • clamp() replaces sizing media queries, but mix a rem term into the preferred value so it still respects font settings
rem for scale, px for hairlines, em for local
/* rem — relative to the ROOT font size, which the user controls.
   1rem = 16px by default, and 24px if they changed it. */
h1 { font-size: 2rem }          /* scales with the user's setting */
.card { padding: 1.5rem }

/* NEVER do this to make rem maths easier — it halves the text size
   for everyone who changed their browser default: */
html { font-size: 62.5% }       /* the "1rem = 10px" trick. Don't. */

/* em — relative to the CURRENT element's font size. Ideal for
   things that must scale with their own text. */
.button { padding: .5em 1em }   /* padding grows with the label */
.badge  { font-size: .75em }    /* three-quarters of its parent */
/* em compounds through nesting, which is the usual gotcha:
   a .badge inside a .badge is .5625em. */

/* px — fixed. Correct for things that should NOT scale: */
border: 1px solid               /* a hairline is a hairline */
box-shadow: 0 1px 2px

/* The practical rule:
     font-size, margin, padding, gap, border-radius  -> rem
     borders, shadows, hairlines                     -> px
     component-internal spacing that tracks its text -> em     */
5

Colour and Typography

Readable text is mostly four decisions — size, line height, line length and contrast — and web fonts are the most common cause of a slow, shifting page.

  • Body text at 1rem, line-height 1.5 unitless, and a measure of 45–75 characters cover most of readability
  • A line-height with units does not scale for children, which is why unitless is the rule
  • font-display: swap avoids invisible text, and a font preload needs crossorigin even same-origin
  • WCAG AA requires 4.5:1 for body text and 3:1 for large text and UI elements — it is a floor, not a preference
  • Never encode meaning in colour alone, and respect prefers-color-scheme and prefers-reduced-motion
Size, line-height, measure
body {
  font-size: 1rem;              /* 16px minimum for body text. Never 12px. */
  line-height: 1.5;             /* unitless, so it scales with font-size */
  max-width: 65ch;              /* 45-75 characters per line */
}
h1 { font-size: 2.5rem; line-height: 1.1 }   /* tighter for large text */

/* line-height with a unit does NOT scale for children — this is why
   unitless is the rule: */
body { line-height: 1.5 }       /* correct */
body { line-height: 24px }      /* a 32px heading also gets 24px */

/* A modular scale keeps sizes coherent rather than arbitrary */
--step-0: 1rem; --step-1: 1.25rem; --step-2: 1.563rem; --step-3: 1.953rem;

/* Other text properties worth knowing */
letter-spacing: .05em           /* only for UPPERCASE labels */
text-wrap: balance              /* even heading line lengths */
text-wrap: pretty               /* no single-word last line in a paragraph */
hyphens: auto
font-variant-numeric: tabular-nums   /* digits align in columns */

/* Do not justify text on the web — it creates rivers of whitespace
   without the hyphenation engine print has. */
6

Custom Properties and Theming

CSS variables are live values that inherit and cascade, which makes theming, dark mode and component variants a matter of redefining tokens rather than rewriting rules.

  • Custom properties are live cascade values, not compile-time substitutions — they inherit and can be redefined per subtree
  • Layer semantic tokens over raw primitives so a palette change touches one place
  • Theming means redefining tokens in three blocks — base, prefers-color-scheme, and an explicit data-theme
  • Defining a colour only inside a dark-mode media query leaves it undefined in light mode
  • They can be read and written from JavaScript, and @property gives them a type so they can animate
Primitives, then semantic tokens
:root {
  /* primitives — the raw palette */
  --blue-600: #2563eb;
  --grey-900: #111827;

  /* semantic tokens — what the UI actually references */
  --color-accent: var(--blue-600);
  --color-text: var(--grey-900);
  --color-surface: #ffffff;

  --space-1: .25rem; --space-2: .5rem; --space-4: 1rem;
  --radius: .5rem;
  --shadow-sm: 0 1px 2px rgb(0 0 0 / .06);
}

.button { background: var(--color-accent); border-radius: var(--radius) }

/* Two layers matters: components reference MEANING, so swapping the
   palette changes one line rather than forty. */

/* Fallbacks for a possibly-undefined variable */
color: var(--color-text, #111)
padding: var(--card-padding, var(--space-4, 1rem))

/* They inherit, so a subtree can override */
.card { --color-accent: var(--green-600) }   /* only inside this card */
Learn this free with Aria, your AI tutor → AiCanCode.org/learn/html-css