CSS#
CSS (Cascading Style Sheets) describes how HTML looks. Modern CSS (“CSS3” in marketing; in practice CSS3 is a long sequence of independently versioned modules) covers layout, animation, theming, and complex selection.
Selectors#
How CSS picks the elements to style. Modern selectors go far
beyond element / class / id, with attribute matchers,
combinators, pseudo-classes, and the newer :is() /
:has() / :where() give the same expressiveness as a
small query language.
/* element */
p { ... }
/* class / id */
.button { ... }
#main { ... }
/* attribute */
input[type="email"] { ... }
a[href^="https://"] { ... } /* starts with */
a[href$=".pdf"] { ... } /* ends with */
/* combinators */
nav a { ... } /* descendant */
nav > a { ... } /* direct child */
h2 + p { ... } /* adjacent sibling */
h2 ~ p { ... } /* subsequent siblings */
/* pseudo-classes */
a:hover, a:focus-visible { ... }
:nth-child(2n) { ... }
input:invalid { ... }
:is(h1, h2, h3) > a { ... }
:where(...) /* same as :is but zero specificity */
:has(img) { ... } /* parent based on its descendants */
/* pseudo-elements */
p::first-line { ... }
p::before { content: "» "; }
Specificity and the Cascade#
When multiple rules match an element, the more specific
one wins; on a tie, the later one wins. The specificity story
is one of CSS’s biggest sources of “why isn’t this working”
debugging time; modern features (:where(), cascade
layers, native nesting) reduce the surface area.
Inline
style="...", highest in the source order.IDs > classes / attributes / pseudo-classes > elements / pseudo-elements.
!important, escape hatch; avoid.
Modern alternatives that reduce specificity wars.
:where(), always zero specificity.Cascade layers (
@layer), explicit cascade ordering.Native nesting, structural without raising specificity.
The Box Model#
Every element is a rectangle with content, padding, border,
and margin. The default box-sizing: content-box makes
width exclude padding and border, which surprises everyone
the first time. Almost every modern stylesheet sets
border-box globally:
*, *::before, *::after { box-sizing: border-box; }
border-box makes width include padding and border, almost always
what you want.
Layout: Flexbox#
For one-dimensional layout, a row or a column. Flexbox handles alignment, distribution, and wrapping along a single axis; reach for it whenever the layout is “items in a line” rather than “rows and columns”:
.row {
display: flex;
gap: 1rem;
align-items: center;
justify-content: space-between;
}
.grow { flex: 1; }
Layout: Grid#
For two-dimensional layout. Grid handles “rows and
columns” and is the right tool for page-scale structure
(header, nav, main, footer), as well as for
auto-fill / minmax patterns that build responsive
card grids without media queries.
.page {
display: grid;
grid-template-columns: 240px 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
"header header"
"nav main"
"footer footer";
min-height: 100vh;
}
header { grid-area: header; }
nav { grid-area: nav; }
main { grid-area: main; }
footer { grid-area: footer; }
Auto layouts.
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
gap: 1rem;
}
Custom Properties (CSS Variables)#
Native CSS variables that cascade, are dynamic at runtime, and can be set per component or per state. The most flexible piece of modern CSS; design tokens, theme switches, and component-local overrides all collapse into one mechanism.
:root {
--bg: white;
--fg: #111;
--accent: #0b3d91;
}
@media (prefers-color-scheme: dark) {
:root { --bg: #111; --fg: #eee; }
}
body { background: var(--bg); color: var(--fg); }
a { color: var(--accent); }
Custom properties cascade, are dynamic at runtime, and can be set per component or even per state.
Modern Color#
Color in CSS has caught up with what designers actually want.
OKLCH is perceptually uniform (a 10% lightness change looks
the same regardless of hue); color-mix() derives hovers
and tints without a preprocessor; wide-gamut color spaces
unlock display capabilities that rgb() couldn’t reach.
/* OKLCH - perceptually uniform, wide gamut */
color: oklch(60% 0.15 250);
color: oklch(60% 0.15 250 / 0.5); /* alpha */
/* color-mix to derive hovers / shades */
background: color-mix(in oklch, var(--accent) 80%, white);
Typography#
The typography defaults a healthy modern stylesheet sets.
system-ui for native-looking fonts, sane line-heights,
clamp() for fluid type that scales with viewport, and
font-display: swap to keep web fonts from causing
invisible-text regressions.
body {
font-family: system-ui, sans-serif;
line-height: 1.5;
font-size: clamp(1rem, 0.95rem + 0.4vw, 1.125rem);
}
h1 { font-size: clamp(1.5rem, 1.2rem + 1vw, 2rem); line-height: 1.2; }
remfor type scales;emfor component-relative spacing.clamp()for fluid type and spacing.font-display: swap;on web fonts to avoid invisible text.
Responsive Design#
Layouts that adapt to screen and container size. Mobile-first media queries are the long-standing default; container queries are the 2023+ addition that lets a component respond to its own width rather than the viewport’s, which finally solves component reuse across layouts.
/* mobile-first */
.grid { grid-template-columns: 1fr; }
@media (min-width: 48rem) {
.grid { grid-template-columns: 1fr 1fr; }
}
/* container queries: respond to container, not viewport */
.card {
container-type: inline-size;
}
@container (min-width: 320px) {
.card-body { display: grid; grid-template-columns: 1fr 1fr; }
}
Transitions and Animations#
CSS handles most micro-animations without JavaScript.
Transitions interpolate between states; @keyframes defines
named animation sequences. The non-negotiable accessibility
addition is prefers-reduced-motion; some users get
motion-sick from animations, and the spec is the standard
opt-out.
.button {
background: var(--bg);
transition: background 0.15s ease;
}
.button:hover { background: var(--bg-hover); }
@keyframes pulse {
from { opacity: 1; }
50% { opacity: 0.5; }
to { opacity: 1; }
}
.loading { animation: pulse 1s infinite ease-in-out; }
Respect prefers-reduced-motion:
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
Logical Properties#
Prefer logical properties for internationalization. Where
margin-left is hard-coded to the visual left, margin-
inline-start follows the writing direction, left in LTR
languages, right in RTL languages. The same pattern applies
to padding, border, and inset properties.
/* directional */
margin-inline-start: 1rem; /* left in LTR, right in RTL */
padding-block: 1rem;
border-inline-end: 1px solid;
Strategies for Big Codebases#
How CSS scales past one stylesheet and one developer. Each strategy below addresses a different failure mode of large CSS, in global namespace collisions, runtime overhead, inconsistent design tokens, third-party style ordering. Most 2026 codebases combine several.
CSS Modules / scoped styles, prevent global collisions.
CSS-in-JS (styled-components, Emotion), co-located with components; runtime cost.
Utility-first (Tailwind), dominant in 2026; class names instead of stylesheets.
Design tokens as custom properties; theming becomes setting variables.
Cascade layers (
@layer), predictable ordering across teams and third-party styles.