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:

SelectorTargets
pAll <p> elements
.classnameAll elements with class="classname"
#idnameThe element with id="idname" (only one per page)
*Every element (use sparingly)
p, h1All <p> AND all <h1>
.card pAll <p> inside an element with class card (descendant)
.card > pAll <p> directly inside .card (immediate child)
a:hoverAn <a> when the mouse is over it (pseudo-class)
button:disabledA disabled button
input::placeholderThe 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 typeSpecificity points
Inline style="..."1000
#id100
.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:

  1. Finds all matching rules
  2. Sorts by specificity (then source order on ties)
  3. Resolves to one value per property
  4. 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

  • margin doesn’t add up like you’d think. Adjacent vertical margins β€œcollapse” β€” the larger wins, they don’t add. Surprises people constantly.

  • display: none vs visibility: hidden vs opacity: 0. First removes from layout entirely. Second hides but keeps space. Third shows space and is still clickable. Different uses.

  • position: absolute removes from flow. The element stops affecting siblings. Useful but disorienting first time.

  • z-index only works on positioned elements. Setting z-index: 9999 on a position: 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.

  • !important cascades 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-block if you want padding to take space.

  • Flexbox children don’t shrink below their content size by default. Set min-width: 0 on flex children if you need them to shrink.

  • 100vh on mobile. Mobile browser address bars are sized inconsistently. The new 100dvh (dynamic viewport height) is the modern fix.

  • CSS class names vs HTML class attribute. In JSX/React, you write className=, not class=. 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

Sources