Last 30 Days
No notifications
CSS (Cascading Style Sheets) controls the visual presentation of HTML elements. Understanding selectors, specificity, the cascade, the box model, and units is essential before tackling layouts.
Inline styles → 1,0,0,0
ID selectors → 0,1,0,0
Class / pseudo → 0,0,1,0
Element / pseudo → 0,0,0,1Higher specificity always wins regardless of source order.
1. !important (avoid when possible) 2. Inline styles 3. ID selectors 4. Class / attribute / pseudo-class selectors 5. Element / pseudo-element selectors 6. Inherited styles
Every element is a rectangular box:
┌─────────────── margin ───────────────┐
│ ┌──────────── border ──────────────┐ │
│ │ ┌────────── padding ──────────┐ │ │
│ │ │ content │ │ │
│ │ └─────────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────┘Set box-sizing: border-box so width includes padding + border.
| Unit | Type | Relative To |
px | Absolute | Screen pixel |
em | Relative | Parent font-size |
rem | Relative | Root font-size |
% | Relative | Parent dimension |
vw/vh | Relative | Viewport width/height |
HTML decides *what* is on the page. CSS decides *how it looks*. Every CSS rule has the same shape:
selector {
property: value;
}For example: "make every paragraph blue" looks like p { color: blue; }. That's it. Everything else in CSS is just choosing better selectors and learning more properties.
1. External file (best — keeps HTML clean):
<link rel="stylesheet" href="styles.css" />
2. Inside a tag (fine for one-off pages):
<style> p { color: red; } </style>
3. Inline on an element (use sparingly — hard to override):
<p style="color: red;">Hi</p>Selectors are how you point at things. Start with these five and you can already style 80% of any page:
| Selector | Example | Targets |
| Element | p { } | Every |
| Class | .card { } | Anything with class="card" |
| ID | #hero { } | The element with id="hero" |
| Descendant | nav a { } | anywhere inside |
| Hover | a:hover { } | A link the mouse is over |
Real-world rule: prefer classes. IDs are too "powerful" (more on that below), and element selectors are too broad.
.card {
color: #222; /* text colour */
background: #f5f5f5; /* fill */
font-size: 1rem; /* text size */
font-weight: 600; /* boldness */
padding: 16px; /* space inside */
margin: 24px; /* space outside */
border: 1px solid #ddd; /* outline */
border-radius: 8px; /* rounded corners */
}This is the single most important picture in CSS:
┌─────────── margin (space OUTSIDE) ───────────┐
│ ┌──────── border (the line) ──────────────┐ │
│ │ ┌───── padding (space INSIDE) ──────┐ │ │
│ │ │ content │ │ │
│ │ └───────────────────────────────────┘ │ │
│ └─────────────────────────────────────────┘ │
└──────────────────────────────────────────────┘By default, when you set width: 200px, that's the *content* width — padding and border are added on top, making the visible box larger. Almost everyone fixes this once and forgets it:
*, *::before, *::after {
box-sizing: border-box;
}Now width: 200px means the whole box is 200 px. Set this in every project.
| Unit | Meaning | Use it for |
px | Fixed pixels | Borders, fine-tuning |
rem | Multiple of root font-size | Font sizes, spacing — *recommended* |
em | Multiple of *parent* font-size | Padding inside a component |
% | Percent of parent | Widths in flexible layouts |
vw / vh | 1% of viewport width / height | Hero sections, full-screen |
Beginner tip: use rem for almost everything. If a user bumps their browser font size for accessibility, your whole layout scales gracefully.
color: red; /* named */
color: #3b82f6; /* hex */
color: rgb(59, 130, 246); /* rgb */
color: rgba(59, 130, 246, 0.5); /* rgb + alpha */
color: hsl(217, 91%, 60%); /* hue/sat/light */HSL is the most human-friendly: change one number, get a related colour.
When two rules target the same element, the browser uses three tie-breakers in this order:
1. Importance — !important beats everything (avoid using it).
2. Specificity — more specific selector wins (next section).
3. Source order — if everything else is equal, the *last* rule wins.
Every selector gets a score (inline, ids, classes, elements). Higher numbers win, left to right.
p { } /* 0,0,0,1 */
.intro { } /* 0,0,1,0 */
p.intro { } /* 0,0,1,1 */
#hero .title { } /* 0,1,1,0 */
style="…" { } /* 1,0,0,0 — inline always loudest */Beginner trap: writing #sidebar p { color: red; } then wondering why .warning { color: orange; } doesn't override it. The ID makes the first rule extra-specific, and the class can't beat it.
Rule of thumb: stick to single-class selectors. Avoid IDs in CSS. Save !important for emergencies.
Some properties pass down to children automatically:
color, font-family, font-size, line-height, text-alignmargin, padding, border, width, height, display, background once and every paragraph picks it up for free.A pseudo-class styles an element in a particular state:
a:hover { color: red; } /* mouse over */
a:focus { outline: 2px solid blue; } /* keyboard focus */
input:disabled { opacity: 0.4; }
li:nth-child(odd) { background: #f9f9f9; }A pseudo-element styles a *piece* of an element (note the double colon):
p::first-line { font-weight: bold; }
.card::before { content: "★"; }Variables make theming and consistency trivial:
:root {
--brand: #3b82f6;
--radius: 8px;
--space-1: 4px;
--space-2: 8px;
}.btn {
background: var(--brand);
border-radius: var(--radius);
padding: var(--space-2);
}
/* Dark mode by overriding ONE scope */
[data-theme="dark"] {
--brand: #60a5fa;
}
Variables cascade like normal CSS, so you can scope them per component.
Browsers ship with built-in styles (huge , default margins on , etc.). Tame them with a one-time reset at the top of your stylesheet:
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
img, video { max-width: 100%; display: block; }
button { font: inherit; cursor: pointer; }em vs rem — The Subtle Trapem compounds inside nested components. If a parent is font-size: 1.2em and the child is also 1.2em, the child renders at 1.44em of the grandparent. rem is always relative to the root, so it never compounds — much more predictable for design systems.
@layer RuleModern CSS lets you create explicit cascade layers, so you can decide that *base* < *components* < *utilities* without fighting specificity:
@layer base, components, utilities;@layer base { p { color: black; } }
@layer utilities { .red { color: red; } }
Anything in a later layer beats earlier layers regardless of selector specificity.
margin-left doesn't make sense if your site supports right-to-left languages like Arabic. Modern CSS uses logical properties:
margin-inline-start: 1rem; /* "start of the line" — left in LTR, right in RTL */
padding-block: 8px; /* top + bottom */color-scheme & accent-colorTwo tiny modern properties that improve UX for free:
html { color-scheme: light dark; } /* native form controls follow OS theme */
:root { accent-color: #6366f1; } /* checkboxes, radios use brand colour */1. Style a card component using only classes (no IDs, no !important).
2. Build a simple light/dark toggle by overriding CSS variables.
3. Take a page that uses px everywhere and convert spacing/font to rem. Bump the browser font size and see how it scales.
4. Use Chrome DevTools → Computed tab and trace why a stubborn rule isn't winning. The "Specificity" tooltip is gold.