CSS β Cascading Style Sheets
Status: π© COMPLETE Last updated: 2026-06-20 Plain-English tagline: The language that controls how HTML looks. Colors, fonts, spacing, layout, animations β all CSS.
In plain English
HTML tells the browser whatβs on the page. CSS tells it what those things look like and where they go.
A line of CSS is a rule: βevery paragraph should have a font size of 16 pixels and be colored dark grey.β The browser applies that rule to every <p> on the page. You write hundreds of rules; together they form the visual design.
CSS is one of the three core web languages (HTML, CSS, JavaScript). Every modern website uses CSS, even if itβs hidden behind tools like Tailwind (which generates CSS under the hood).
CSS is officially a W3C standard, currently evolving rapidly β features like CSS variables, Grid, Flexbox, container queries, and the new :has() selector make modern CSS dramatically more capable than the 2010 version.
Why it matters
Even with Tailwind doing most of the styling for you, knowing CSS pays off:
- Debugging visual bugs requires reading the actual computed CSS in DevTools
- Animations and transitions are CSS at heart
- Layout problems trace back to CSS box model, flexbox, or grid behavior
- Responsive design is CSS media queries (which Tailwind wraps but doesnβt hide)
- Reading other peopleβs code assumes CSS knowledge
You donβt need to be a CSS expert. But you should be able to read CSS, modify it, and recognize the major concepts.
The basic syntax
A CSS rule has two parts: a selector and a block of declarations:
p {
color: #333;
font-size: 16px;
line-height: 1.5;
}pβ the selector (βany<p>elementβ)color: #333β a declaration (property: value)- The block is wrapped in
{ }
Multiple selectors share a block by separating with commas:
h1, h2, h3 {
font-family: serif;
margin-top: 1em;
}Where CSS lives
Three places:
1. External stylesheet (most common)
A .css file linked from HTML:
<link rel="stylesheet" href="/styles.css">2. <style> tag in the HTML
<style>
body { background: #fff; }
</style>3. Inline style attribute
<p style="color: red;">Hello</p>External is the default for projects. Inline is for one-off cases. The <style> tag is mostly for examples or framework-injected styles.
In React/Next.js, you typically import a CSS file or use CSS-in-JS or Tailwind utility classes β but it all becomes CSS in the browser.
Selectors β how you target elements
The most-used selectors:
| Selector | Targets |
|---|---|
p | All <p> elements |
.classname | All elements with class="classname" |
#idname | The element with id="idname" (only one per page) |
* | Every element (use sparingly) |
p, h1 | All <p> AND all <h1> |
.card p | All <p> inside an element with class card (descendant) |
.card > p | All <p> directly inside .card (immediate child) |
a:hover | An <a> when the mouse is over it (pseudo-class) |
button:disabled | A disabled button |
input::placeholder | The placeholder text of an input (pseudo-element) |
[href^="https"] | All elements with an href attribute starting with https |
:has(img) | Modern: any element that contains an <img> |
Selector specificity matters: more specific selectors win when rules conflict.
The box model β every element is a box
Every HTML element is rendered as a rectangular box. The box has four concentric parts:
βββββββββββββββββββββββββββββββββββ
β margin β β space OUTSIDE the box
β βββββββββββββββββββββββββββββ β
β β border β β
β β βββββββββββββββββββββββ β β
β β β padding β β β β space INSIDE the border
β β β βββββββββββββββββ β β β
β β β β content β β β β β the actual element content
β β β βββββββββββββββββ β β β
β β βββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββ
- margin β space between this box and others
- border β visible border line
- padding β space inside the border, around the content
- content β the actual width/height of the element
You set each:
.box {
margin: 16px; /* space outside */
padding: 8px; /* space inside the border */
border: 1px solid #ccc;
width: 200px; /* width of the content area */
}box-sizing: border-box β flips this so width includes padding and border (more intuitive). Set globally:
* { box-sizing: border-box; }This is the modern default and what Tailwind/most frameworks assume.
Layout: Flexbox
For arranging items in a single row or column. The dominant layout tool of 2026.
.row {
display: flex;
gap: 16px; /* space between children */
justify-content: space-between; /* horizontal distribution */
align-items: center; /* vertical alignment */
}Children of .row lay out horizontally. Properties on the parent control distribution; properties on children control their individual behavior.
Tailwind equivalent: flex gap-4 justify-between items-center.
Layout: Grid
For two-dimensional layouts (rows AND columns).
.grid {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* three columns: 1, 2, 1 unit wide */
gap: 16px;
}Use cases: full-page layouts, image galleries, cards. Less common than flexbox for everyday work but irreplaceable when you need it.
Responsive design β media queries
CSS that applies only at certain screen sizes:
/* Default styles (mobile-first) */
.card { padding: 8px; }
/* Larger screens */
@media (min-width: 768px) {
.card { padding: 16px; }
}Tailwind wraps this in prefixed classes: p-2 md:p-4.
The mobile-first convention: write your base styles for the smallest screen, then add media queries for larger ones. Easier than the reverse.
CSS variables (custom properties)
Reusable values defined in CSS:
:root {
--color-primary: #2563eb;
--spacing-base: 16px;
}
.button {
background: var(--color-primary);
padding: var(--spacing-base);
}Variables can be redefined in nested scopes (e.g. .dark { --color-primary: ...; }) β useful for dark mode.
Specificity β when rules conflict
When two rules target the same element, the more specific one wins:
| Selector type | Specificity points |
|---|---|
Inline style="..." | 1000 |
#id | 100 |
.class, :hover, [attr] | 10 |
p, div (element) | 1 |
Add them up. #nav .item a:hover = 100 + 10 + 1 + 10 = 121.
!important overrides everything (use sparingly β itβs an escape hatch, not a tool).
When two rules have the same specificity, the later one wins (source order).
The βcascadingβ in CSS
When determining a style for an element, the browser:
- Finds all matching rules
- Sorts by specificity (then source order on ties)
- Resolves to one value per property
- Applies inherited properties from parents (some β like color, font β inherit; most donβt)
The βcascadeβ is this resolution process. Understanding it explains why βmy style isnβt applying.β
A concrete example: styling a card
<article class="card">
<h2>Title</h2>
<p>Some text.</p>
<button>Learn more</button>
</article>.card {
background: white;
border: 1px solid #e5e7eb;
border-radius: 8px;
padding: 24px;
max-width: 400px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card h2 {
margin: 0 0 8px;
font-size: 1.25rem;
color: #111827;
}
.card p {
color: #6b7280;
line-height: 1.6;
margin: 0 0 16px;
}
.card button {
background: #2563eb;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.card button:hover {
background: #1d4ed8;
}A polished card from ~20 lines of CSS.
The Tailwind version:
<article class="bg-white border border-slate-200 rounded-lg p-6 max-w-md shadow-sm">
<h2 class="text-xl font-semibold text-slate-900 mb-2">Title</h2>
<p class="text-slate-500 leading-relaxed mb-4">Some text.</p>
<button class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded">Learn more</button>
</article>Same result. No separate CSS file. Trade-off: more verbose HTML, no separate stylesheet to maintain. Most modern projects choose Tailwind for this reason.
Common gotchas
-
margindoesnβt add up like youβd think. Adjacent vertical margins βcollapseβ β the larger wins, they donβt add. Surprises people constantly. -
display: nonevsvisibility: hiddenvsopacity: 0. First removes from layout entirely. Second hides but keeps space. Third shows space and is still clickable. Different uses. -
position: absoluteremoves from flow. The element stops affecting siblings. Useful but disorienting first time. -
z-indexonly works on positioned elements. Settingz-index: 9999on aposition: static(default) element does nothing. -
Specificity surprises. A rule that should override another doesnβt because the other has higher specificity. Use DevToolsβ βComputedβ tab to see what won and why.
-
!importantcascades down. It overrides not just other declarations but also more specific selectors. Avoid in shared code. -
Padding on inline elements doesnβt push other content. Use
display: inline-blockif you want padding to take space. -
Flexbox children donβt shrink below their content size by default. Set
min-width: 0on flex children if you need them to shrink. -
100vhon mobile. Mobile browser address bars are sized inconsistently. The new100dvh(dynamic viewport height) is the modern fix. -
CSS class names vs HTML class attribute. In JSX/React, you write
className=, notclass=. CSS itself doesnβt care. -
Cache busting. When you change CSS, browsers may serve the old version. Hard refresh (Ctrl+Shift+R) or version your filenames.
Modern CSS worth knowing (2026)
:has()β parent selector.div:has(img)= any div containing an img. Powerful.- Container queries β
@container (min-width: 400px)β style based on container size, not viewport. gapβ works in flexbox now too, not just grid. Use it everywhere.@scopeβ scope styles to a component subtree.- Cascade layers β
@layer base, components, utilitiesβ Tailwind v4 uses this for layering. color-mix()βcolor-mix(in srgb, red, white)β mix two colors at runtime.accent-colorβ recolor form controls (checkboxes, radios) with one property.
You donβt need all of these. But knowing they exist lets you reach for them when relevant.
See also
- HTML π©
- JavaScript π©
- Tailwind CSS π©
- ui π©
- The DOM π©
- Responsive design π©
- Dark mode π©
- Accessibility (a11y) π©
- Color theory for devs π©
- Glossary: CSS
Sources
- MDN β CSS reference β canonical
- CSS Tricks β Complete Guide to Flexbox
- CSS Tricks β Complete Guide to Grid
- web.dev β Learn CSS β Googleβs free course