CSS Cheat Sheet
The language that styles web pages: selectors, the cascade, box model, flexbox, grid, responsive design and modern features. Browser support notes use web.dev Baseline status as of October 2026.
On this page
Selectors #
| Selector | Matches |
|---|---|
p | Every <p> element (type selector) |
.card | Elements with class="card" |
#main | The element with id="main" (high specificity, prefer classes) |
[href] / [type="email"] | Has the attribute / has that exact value |
[href^="https"] / [href$=".pdf"] / [class*="btn"] | Value starts with / ends with / contains |
h1, h2 | Both h1 and h2 (selector list) |
.nav a | Any a inside .nav (descendant) |
.nav > a | Only direct children |
h2 + p | The p right after an h2 (next sibling) |
h2 ~ p | Every p sibling after an h2 |
a:hover / a:active | Mouse is over it / being clicked |
:focus-visible | Focused and the browser shows a focus ring (keyboard); use instead of :focus for outlines |
:focus-within | The element or something inside it has focus |
li:first-child / li:last-child | First / last child of its parent |
li:nth-child(odd) / li:nth-child(3n) | Odd children / every 3rd child |
:not(.active) | Anything that doesn't match |
:is(h1, h2, h3) a | Shorter selector lists; specificity of the most specific argument |
:where(ul, ol) | Like :is() but zero specificity: easy to override |
.card:has(img) | A card that contains an image: the "parent selector" (widely available since 2026) |
input:checked / :disabled / :required | Form element states |
input:user-invalid | Invalid only after the user has interacted (widely available) |
::before / ::after | Generated content; needs content: "" |
::placeholder / ::selection / ::marker | Placeholder text / selected text / list bullet or number |
Specificity & Cascade #
| Syntax | Meaning |
|---|---|
#id | Specificity (1, 0, 0) |
.class / [attr] / :hover | (0, 1, 0) |
p / ::before | (0, 0, 1) |
* / :where() | (0, 0, 0) |
:is() / :not() / :has() | Count as their most specific argument |
style="..." | Inline styles beat every selector |
color: red !important | Beats all normal declarations; avoid outside utility classes |
@layer reset, base, components; | Declare layer order: later layers win |
@layer base { a { color: blue; } } | Put rules in a layer; unlayered styles beat layered ones |
inherit / initial / unset / revert | Take the parent's value / the CSS default / either / the browser default |
Conflict order: !important → layer → specificity → source order (last one wins). Text properties (color, font-*) inherit, box properties (margin, border) don't.
Box Model #
| Code | What it does |
|---|---|
*, *::before, *::after { box-sizing: border-box; } | width includes padding and border (put it in every project) |
margin: 10px | All four sides |
margin: 10px 20px | Top + bottom, left + right |
margin: 10px 20px 30px | Top, left + right, bottom |
margin: 10px 20px 30px 40px | Top, right, bottom, left (clockwise) |
margin-inline: auto | Center a block that has a width or max-width |
padding: 1rem 2rem | Inner space; same shorthand rules as margin |
border: 1px solid #ccc | Width, style, colour |
border-radius: 8px / border-radius: 50% | Rounded corners / circle (on a square) |
outline: 2px solid; outline-offset: 2px | Like a border but takes no space (focus rings) |
overflow: auto | Scrollbar only when needed (hidden clips, clip clips without scrolling) |
Vertical margins between blocks collapse: only the larger one is used. This doesn't happen in flex or grid.
Units & Colors #
| Value | Meaning |
|---|---|
px | Pixels (borders, shadows) |
rem | Multiple of the root font size (16px by default); use for font sizes and spacing |
em | Multiple of the element's font size (in font-size itself: the parent's); padding that scales with text |
% | Percentage of the parent (for width: of the parent's width) |
vw / vh | 1% of the viewport width / height |
dvh / svh / lvh | Dynamic / small / large viewport height: fixes 100vh on mobile (widely available) |
ch | Width of the "0" character: max-width: 65ch gives readable lines |
fr | A share of the free space in a grid |
#0b5fff / #0b5fff80 | Hex colour / with alpha |
rgb(11 95 255 / 50%) | Modern syntax, alpha after / (old: rgba(11, 95, 255, 0.5)) |
hsl(220 100% 52%) | Hue, saturation, lightness |
oklch(60% 0.2 260) | Lightness, chroma, hue: even-looking palettes, wide gamut (widely available) |
currentColor | The element's current color (icons, borders) |
color-mix(in oklch, var(--brand), white 20%) | Mix two colours (widely available) |
light-dark(#111, #eee) | Light-mode value, dark-mode value; needs color-scheme: light dark (Baseline 2024, all current browsers) |
Typography #
| Code | What it does |
|---|---|
font-family: system-ui, sans-serif | Font stack: first available font wins |
font-size: 1.125rem | Text size |
font-weight: 400 / font-weight: 700 | Normal / bold |
line-height: 1.5 | Unitless = multiple of the font size (best choice) |
text-align: center | Also start, end, justify |
text-decoration: none | Remove the underline from links |
white-space: nowrap | Never wrap the text |
text-overflow: ellipsis | "Long text…" (needs overflow: hidden and white-space: nowrap) |
overflow-wrap: anywhere | Break long words or URLs instead of overflowing |
text-wrap: balance | Even line lengths for headings (Baseline 2024) |
text-wrap: pretty | Avoid a lonely last word in paragraphs (Chromium and Safari 26+ only; safe to add) |
@font-face {
font-family: "Inter";
src: url("/fonts/inter.woff2") format("woff2");
font-weight: 100 900; /* variable font */
font-display: swap; /* show fallback text while loading */
}
Display & Positioning #
| Code | What it does |
|---|---|
display: block | Full width, starts on a new line |
display: inline | Flows with text; ignores width and height |
display: inline-block | Flows with text but accepts width, height and padding |
display: none | Removed from layout and from screen readers |
position: static | Default; top, left and z-index don't apply |
position: relative | Offset from its normal spot; anchor for absolute children |
position: absolute | Out of the flow; placed against the nearest positioned ancestor |
position: fixed | Pinned to the viewport (a transform on an ancestor breaks this) |
position: sticky; top: 0 | Scrolls normally, then sticks at top inside its parent |
inset: 0 | Shorthand for top, right, bottom, left |
z-index: 10 | Stacking order for positioned, flex and grid items |
isolation: isolate | New stacking context: keeps child z-index values local |
Flexbox #
| Code | What it does |
|---|---|
display: flex | Container: lay children out in a row |
flex-direction: column | Container: stack vertically (main axis becomes vertical) |
flex-wrap: wrap | Container: allow items to wrap onto more lines |
justify-content: space-between | Container: spread along the main axis (center, flex-start, flex-end, space-evenly) |
align-items: center | Container: align on the cross axis (default stretch) |
gap: 1rem | Container: space between items |
flex: 1 | Item: grow to share free space equally (short for flex: 1 1 0%; longhands flex-grow, flex-shrink, flex-basis) |
flex: 0 0 200px | Item: fixed 200px, no growing or shrinking |
align-self: flex-end | Item: override align-items for one item |
order: -1 | Item: move visually (tab order stays the same) |
margin-left: auto | Item: push it and the items after it to the end |
min-width: 0 | Item: allow shrinking below its content (fixes overflow) |
Grid #
| Code | What it does |
|---|---|
display: grid | Container: two-dimensional layout |
grid-template-columns: 200px 1fr | Fixed sidebar + flexible column |
grid-template-columns: repeat(3, 1fr) | Three equal columns |
repeat(auto-fit, minmax(250px, 1fr)) | Responsive cards with no media query |
auto-fill / auto-fit | Keep empty tracks / collapse them so items stretch |
grid-template-rows: auto 1fr auto | Header, content, footer |
grid-auto-rows: minmax(100px, auto) | Size of rows created automatically |
gap: 1rem 2rem | Row gap, column gap |
place-items: center | Center every item in its cell |
grid-column: 1 / -1 | Item: span from the first to the last line (full width) |
grid-column: span 2 | Item: take two columns |
grid-area: sidebar | Item: put it in a named area |
justify-self: end / align-self: start | Item: position inside its cell |
grid-template-columns: subgrid | Item: reuse the parent's columns (widely available since 2026) |
.layout {
display: grid;
grid-template-columns: 220px 1fr;
grid-template-areas:
"header header"
"sidebar main"
"footer footer";
}
header { grid-area: header; } aside { grid-area: sidebar; }
main { grid-area: main; } footer { grid-area: footer; }
Responsive Design #
| Code | What it does |
|---|---|
@media (width >= 768px) { } | Range syntax (widely available); older: (min-width: 768px) |
@media (prefers-color-scheme: dark) | User has dark mode on |
@media (hover: hover) | Device has a real hover (mouse, trackpad) |
container-type: inline-size | Make an element a container that children can query |
container: card / inline-size | Same, with a name |
@container card (width > 400px) { } | Style by the container's width, not the screen (widely available) |
clamp(1rem, 0.8rem + 1vw, 1.5rem) | Fluid value: minimum, preferred, maximum |
width: min(100% - 2rem, 70rem) | The smaller of the two (max() takes the larger) |
img { max-width: 100%; height: auto; } | Images never overflow their container |
Write mobile-first: base styles for small screens, then add @media (width >= ...) for bigger ones.
Custom Properties #
| Code | What it does |
|---|---|
:root { --brand: #0b5fff; } | Define a global variable |
color: var(--brand) | Use it |
var(--gap, 1rem) | With a fallback if it isn't set |
.dark { --brand: #7aa2ff; } | Override for an element and its children |
padding: calc(var(--space) * 2) | Do maths with variables |
el.style.setProperty("--x", "10px") | Set a variable from JavaScript |
@property --angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; } | Typed variable that can be animated (Baseline 2024) |
var() doesn't work inside a media query condition: @media (width >= var(--bp)) never matches.
Modern Features #
| Code | What it does |
|---|---|
.card { &:hover { } } | Nesting with & (widely available since 2026) |
.card { .title { } } | Nested descendant, & optional |
form:has(:user-invalid) button | Style based on children or later state (widely available since 2026) |
aspect-ratio: 16 / 9 | Keep a ratio (videos, cards) (widely available) |
margin-inline / padding-block | Logical properties: follow text direction (left/right, top/bottom in English) |
inline-size / block-size | Logical width / height |
@scope (.card) to (.content) { img { } } | Styles that only apply inside a part of the page (Baseline 2026, newly available) |
field-sizing: content | Inputs and textareas grow with their content (Baseline since June 2026) |
color-scheme: light dark | Dark scrollbars and form controls in dark mode; required for light-dark() |
Transitions & Animations #
| Code | What it does |
|---|---|
transition: opacity 200ms ease-out | Animate changes to opacity |
transition: transform 0.3s, opacity 0.3s | Several properties (avoid all) |
transform: translateY(-4px) scale(1.05) | Move, scale, rotate without affecting layout |
translate: 0 -4px / rotate: 45deg / scale: 1.1 | Separate transform properties (widely available) |
animation: fade-in 300ms ease-out both | Name, duration, easing, fill mode (add infinite to loop) |
@starting-style { } | Start values for an element that just appeared (Baseline 2024) |
transition-behavior: allow-discrete | Let display and popovers fade out (Baseline 2024) |
@keyframes fade-in {
from { opacity: 0; translate: 0 8px; }
to { opacity: 1; translate: 0 0; }
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}
Animate transform and opacity where possible: they're cheap. Animating width, top or margin causes layout work on every frame.
Centering Recipes #
/* Both axes: grid */
.parent { display: grid; place-items: center; }
/* Both axes: flexbox */
.parent { display: flex; justify-content: center; align-items: center; }
/* Vertically in a plain block, no flex or grid (Baseline 2024) */
.parent { align-content: center; }
/* Horizontally: a block with a max width */
.content { max-width: 60ch; margin-inline: auto; }
/* On top of a positioned parent */
.child { position: absolute; top: 50%; left: 50%; translate: -50% -50%; }
Tips #
z-indexdoes nothing? The element must be positioned (or a flex/grid item), and it only competes inside its stacking context:transform,opacitybelow 1 andisolationcreate a new one.100vhis too tall on mobile because of the browser bars: use100dvhormin-height: 100svh.- A flex or grid child overflows with long text: give it
min-width: 0(in grid, useminmax(0, 1fr)). position: stickynot sticking: it needstop(orbottom), and an ancestor withoverflow: hiddenorautobecomes its scroll box.- Never remove focus outlines without a replacement: style
:focus-visibleinstead of writingoutline: none. - Specificity wars: keep selectors to one class, use
:where()for defaults and@layerfor order instead of piling on!important.
Nothing on this sheet matches “”.