Short answer: Modern CSS covers most layout and theming without a preprocessor. Use flexbox for one-dimensional rows or columns and grid for two-dimensional layouts. Custom properties (--brand) handle theming, and container queries (@container) let components respond to their own width. :has() selects a parent by its contents, and native nesting (&:hover) works in all major browsers. The tables below give the syntax for each, with the specificity rules you need to debug styles.
The examples in this CSS cheat sheet were run in headless Chromium 141 on 9 October 2026, checking the computed styles for each selector, layout, custom property, container query and nesting rule. Support notes for other browsers come from MDN.
Table of Contents
Selectors and specificity
| Selector | Matches |
|---|---|
p, .card, #main | Type, class, ID |
[href^="https"], [href$=".pdf"], [data-state="open"] | Attribute starts with, ends with, equals |
nav a, ul > li, h2 + p, h2 ~ p | Descendant, direct child, next sibling, any later sibling |
a:hover, button:focus-visible, input:invalid | State pseudo-classes. :focus-visible shows focus rings for keyboard users only |
li:nth-child(2n+1), li:first-child, li:last-child | Position |
:not(.active), :is(h1, h2, h3) | Negation and grouping; specificity of the most specific argument |
:where(ul, ol) li | Like :is() but with zero specificity, so it is easy to override |
p::first-line, .req::after { content: "*"; } | Pseudo-elements |
Specificity is counted as (IDs, classes/attributes/pseudo-classes, types). #nav a (1,0,1) beats .menu a.link (0,2,1). When specificity ties, the rule that comes later wins. Avoid !important except for utilities.
The :has() selector
:has() selects an element based on what it contains or what follows it, which CSS could not do before. It has been in all major browsers since December 2023.
| Selector | Matches |
|---|---|
.card:has(img) | Cards that contain an image |
form:has(input:invalid) button[type="submit"] | The submit button of a form with an invalid field |
h2:has(+ p) | An h2 immediately followed by a paragraph |
label:has(input:checked) | A label whose checkbox is ticked |
body:has(dialog[open]) | The page while a dialog is open, for example to stop scrolling |
.grid:not(:has(*)) | An empty grid |
Box model, units and sizing
*, *::before, *::after { box-sizing: border-box; }
.wrapper {
max-width: 70rem;
margin-inline: auto; /* centre horizontally */
padding: 1rem clamp(1rem, 4vw, 3rem);
}
.hero { min-height: 100dvh; }
| Value | Meaning |
|---|---|
box-sizing: border-box | Width includes padding and border |
margin: 1rem 2rem | Shorthand: top/bottom, then left/right. Four values go clockwise from the top |
margin-inline, padding-block, inset: 0 | Logical properties; follow the writing direction |
rem, em, ch | Root font size, current font size, width of the 0 character |
vw, vh, dvh, svh | Viewport units. dvh follows mobile browser bars as they show and hide |
clamp(1rem, 2.5vw, 2rem) | Fluid value with a minimum and a maximum |
min(100%, 40rem), max(), calc(100% - 2rem) | Maths functions |
aspect-ratio: 16 / 9 | Keep proportions without padding hacks |
Flexbox
| Property | What it does |
|---|---|
display: flex | One-dimensional layout of the children (a row by default) |
flex-direction: column | Stack vertically |
justify-content: space-between | Spread items along the main axis (center, flex-start, space-around also work) |
align-items: center | Align on the cross axis; vertical centring in a row |
flex-wrap: wrap | Let items move to a new line |
gap: 1rem | Space between items, not around the edges |
flex: 1 | Grow to share free space equally |
flex: 0 0 15rem | Fixed 15rem item that does not grow or shrink |
margin-left: auto | Push an item, and everything after it, to the far end |
align-self, order | Override alignment or visual order for one item |
Grid
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
gap: 1.5rem;
}
.layout {
display: grid;
grid-template-columns: 14rem 1fr;
grid-template-areas:
"side header"
"side main";
}
.layout > header { grid-area: header; }
| Property | What it does |
|---|---|
grid-template-columns: 1fr 2fr | Two columns sharing space 1 : 2 |
repeat(auto-fit, minmax(15rem, 1fr)) | As many columns of at least 15rem as fit, with no media queries |
grid-column: 1 / -1 | Span from the first to the last line |
grid-column: span 2 | Span two columns |
grid-template-areas and grid-area | Name regions and place children by name |
place-items: center | Centre content in both directions |
grid-template-rows: subgrid | Child grid uses the parent’s tracks, so card rows line up |
Custom properties
:root {
--brand: #0b6bcb;
--radius: 0.5rem;
}
.button {
background: var(--brand);
border-radius: var(--radius, 4px); /* fallback if unset */
}
.dark .button { --brand: #5aa9ff; } /* override in a scope */
| Feature | What it does |
|---|---|
--name: value | Declare; inherited by descendants |
var(--name, fallback) | Use, with an optional fallback |
@property --angle { syntax: "<angle>"; inherits: false; initial-value: 0deg; } | Typed property that can be animated. Baseline since 2024 |
color-mix(in srgb, var(--brand) 80%, white) | Tint or shade a variable |
color-scheme: light dark with light-dark(#111, #eee) | Pick a value for the light or dark theme |
Custom properties are case-sensitive and resolved when used, not when declared. They work in JavaScript too: el.style.setProperty('--brand', 'red').
Media and container queries
| Syntax | What it does |
|---|---|
@media (width >= 48rem) { ... } | Range syntax; same as (min-width: 48rem) |
@media (prefers-color-scheme: dark) | Dark mode set in the operating system |
@media (prefers-reduced-motion: reduce) | Turn off animations for people who ask for it |
@media (hover: hover) and (pointer: fine) | Mouse or trackpad, not touch |
container-type: inline-size | Make an element a size container for its children |
container: card / inline-size | Shorthand: name and type |
@container card (width >= 30rem) { ... } | Style children by the container’s width, not the viewport |
cqi, cqw | Units relative to the container’s size |
.card-wrap { container: card / inline-size; }
@container card (width >= 30rem) {
.card { display: grid; grid-template-columns: 10rem 1fr; }
}
Container size queries are supported in all major browsers. Style queries on custom properties (@container style(--theme: dark)) are much newer: Firefox added them in version 151 in May 2026, so plan a fallback for older browsers.
Native CSS nesting
.card {
padding: 1rem;
& h2 { margin-top: 0; } /* .card h2 */
&:hover { outline: 2px solid var(--brand); } /* .card:hover */
.dark & { background: #222; } /* .dark .card */
@media (width >= 48rem) {
padding: 2rem;
}
}
| Rule | Detail |
|---|---|
& is optional before a descendant | h2 { } inside .card also means .card h2 |
& is required for compound selectors | &:hover and &.active; without &, a space is implied |
| No string concatenation | Sass-style &__title does not work; write .card__title in full |
| Specificity | & behaves like :is() with the parent selector list |
| Support | All major browsers since December 2023; older browsers ignore nested rules |
Official documentation (checked 9 October 2026): MDN: CSS · W3C: CSS current work · MDN: CSS flexible box layout · MDN: CSS grid layout · MDN: Container size and style queries · MDN: :has() · MDN: Using CSS nesting
CSS Cheat Sheet at a glance


Related: HTML Cheat Sheet: Elements, Attributes and Forms · .
Reading a lot of our guides? Go ad-free with srvScripts Pro for $6 a month. It also pays for new guides. Cancel any time.