This cheat sheet covers what you look up most often: selectors and specificity (ID-CLASS-TYPE), the rem, em, %, vw, dvh and ch units and clamp(), box-sizing and positioning, Flexbox and Grid, custom properties, media and container queries, and centering recipes. Every code snippet was checked in a browser, and support for newer features is given as Baseline status on the check date.
After this you will be able to
- calculate a selector’s specificity, including
:is(),:where()and:has(), and tell which rule wins - pick a unit (
rem,em,%,dvh,ch) and set a fluid size withclamp() - find the Flexbox or Grid property for the job and centre a box with one of the tested recipes
Know beforehand
Verified with
Microsoft Edge 155 + Playwright 1.64web-features 3.42 (Baseline)
Checked against docs: October 12, 2026
Contents
Selectors: from simple ones to :is(), :where() and :has()
A selector tells the browser which elements a rule applies to. Combinators (space, >, +, ~) relate selectors by their position in the document tree, pseudo-classes (:hover) describe a state, and pseudo-elements (::before) target a part of an element that is not in the HTML.
*
- What it selects
- every element
- Example
* { margin: 0; }
p
- What it selects
- elements by tag
- Example
p { line-height: 1.6; }
.card
- What it selects
- elements with a class
- Example
.card { padding: 16px; }
#app
- What it selects
- the element with an
id - Example
#app { min-height: 100dvh; }
[type="email"]
- What it selects
- by attribute and value
- Example
input[type="email"]
A B
- What it selects
Banywhere insideA- Example
nav a
A > B
- What it selects
Bas a direct child ofA- Example
ul > li
A + B
- What it selects
Bright afterA- Example
h2 + p
A ~ B
- What it selects
- every
BafterAon the same level - Example
h2 ~ p
:hover, :focus-visible
- What it selects
- element state
- Example
a:hover,button:focus-visible
:nth-child(2n)
- What it selects
- by position among siblings
- Example
tr:nth-child(2n)
::before, ::after
- What it selects
- a pseudo-element before or after the content
- Example
.req::after { content: "*"; }
:is(A, B)
- What it selects
- any item in the list; weight of the heaviest argument
- Example
:is(h1, h2) a
:where(A, B)
- What it selects
- the same, with zero weight
- Example
:where(ul, ol) li
:not(A)
- What it selects
- everything that does not match
A - Example
li:not(:last-child)
A:has(B)
- What it selects
Athat contains or is followed byB- Example
.card:has(img)
:has() was the first way to select a parent or a previous element by what comes after it. Three common uses — a card layout that depends on an image, a highlighted label for an empty field, and a “disabled-looking” submit button while the form has errors:
/* A card with an image gets two columns, a card without one stays as is */
.card:has(img) {
display: grid;
grid-template-columns: 120px 1fr;
gap: 16px;
}
/* The label above an empty required field */
label:has(+ input:invalid) {
color: crimson;
}
/* The submit button fades while the form has errors */
form:has(:invalid) button[type="submit"] {
opacity: 0.5;
}display: grid only on the card with an <img>, a crimson label and a button with opacity: 0.5 while the required field is empty; back to normal after typing.Specificity: how to calculate a selector’s weight
When several rules set the same property, the browser compares specificity — three numbers, ID-CLASS-TYPE. Columns are compared left to right: one ID beats any number of classes. With equal weight, the rule that comes later wins. Specificity is only compared within the same origin and cascade layer.
*
- Weight
- 0-0-0
- Why
- the universal selector adds no weight
ul li
- Weight
- 0-0-2
- Why
- two tags; combinators do not count
.item, [type="email"], :hover
- Weight
- 0-1-0
- Why
- class, attribute and pseudo-class share one column
li.item
- Weight
- 0-1-1
- Why
- class + tag
a::before
- Weight
- 0-0-2
- Why
- a pseudo-element counts as a tag
#nav .item
- Weight
- 1-1-0
- Why
- an ID beats even 11 classes
:not(.active)
- Weight
- 0-1-0
- Why
:not()itself has no weight; its argument does
:is(#nav, .menu) li
- Weight
- 1-0-1
- Why
- the heaviest argument counts, even if
.menumatched
:where(#nav) li
- Weight
- 0-0-1
- Why
:where()is always 0
style="…"
- Weight
- above any selector
- Why
- only
!importantoverrides it
nav a { color: gray; } /* 0-0-2 */
.menu a { color: black; } /* 0-1-1 — stronger */
:where(.menu) a { color: red; } /* 0-0-1 — :where() adds no weight */
:is(.menu, #top) a { color: blue; } /* 1-0-1 — weight of the heaviest argument */<nav class="menu"><a>. In Edge 155 the link is blue: :is(.menu, #top) got ID weight even though there is no #top on the page. Without the last line it is black.- Conflict resolution order: importance (
!important) and origin →@layer→ specificity → source order. - Unlayered styles beat styles in any
@layer; of two layers, the one declared later wins. - Write resets and base styles with
:where()so any class overrides them without a weight race. !importantand IDs in component styles are a last resort: only the same weapons can override them.
Units and sizing functions
px
- Relative to
- a CSS pixel
- Good for
- borders, shadows, small exact sizes
rem
- Relative to
- the root (
html)font-size; usually 16px by default, the user can change it - Good for
- font sizes, spacing, breakpoints
em
- Relative to
- the element’s own
font-size; insidefont-size— the parent’s - Good for
- button
padding, an icon next to text
%
- Relative to
- the containing block;
paddingandmarginin % use its width - Good for
- column widths, proportional spacing
vw, vh
- Relative to
- 1% of the window width / height;
vhequalslvh - Good for
- full-screen blocks on desktop
dvh, svh, lvh
- Relative to
- 1% of the dynamic, small or large window height — aware of mobile browser toolbars
- Good for
- the first screen on a phone
ch
- Relative to
- the width of the “0” glyph in the element’s font
- Good for
- text
max-width
fr
- Relative to
- a share of free space in Grid
- Good for
- grid columns
cqi, cqw
- Relative to
- 1% of the query container size
- Good for
- typography inside a component
html { font-size: 100%; } /* 1rem = the browser setting, usually 16px */
.title { font-size: clamp(1.5rem, 1rem + 2.5vw, 3rem); } /* 24px…48px */
.text { max-width: 65ch; } /* about 65 characters per line */
.btn { font-size: 1.25rem; padding: 0.5em 1em; } /* em = its own font-size: 10px 20px */
.hero { min-height: 100dvh; } /* window height, aware of mobile browser toolbars */65ch equals the width of 65 zeros (520px in the default font); the button has padding: 10px 20px; .hero equals the window height.clamp(MIN, PREFERRED, MAX) returns the preferred value while it stays between the bounds. min(a, b) takes the smaller value, max(a, b) the larger, and calc() mixes units: calc(100% - 2rem).
Box model and positioning
A box consists of content, padding, border and margin. By default (content-box) width sets only the content, and padding and border are added on top. With border-box, width is the outer size up to and including the border.
*, *::before, *::after {
box-sizing: border-box;
}
.box {
width: 300px;
padding: 20px;
border: 2px solid;
/* border-box: 300px outside, 300 − 40 − 4 = 256px for content */
/* content-box: it would be 300 + 40 + 4 = 344px outside */
}.box is 300px wide with 256px for content; with content-box the same box is 344px.static
- In the flow?
- yes
top/leftrelative to- they have no effect
- Typical use
- the default
relative
- In the flow?
- yes, its space is kept
top/leftrelative to- its normal place
- Typical use
- a reference point for
absolutechildren
absolute
- In the flow?
- no
top/leftrelative to- the nearest ancestor whose
positionis notstatic - Typical use
- a badge, a close button
fixed
- In the flow?
- no
top/leftrelative to- the window; an ancestor with
transform/filterinstead, if there is one - Typical use
- a floating button, an overlay
sticky
- In the flow?
- yes
top/leftrelative to- the nearest scrolling ancestor; needs
topor another inset - Typical use
- a table header, a menu
.card { position: relative; } /* reference point for absolute children */
.badge { position: absolute; top: 8px; right: 8px; }
.header { position: sticky; top: 0; } /* sticks to the top while scrolling */
.overlay {
position: fixed;
inset: 0; /* = top: 0; right: 0; bottom: 0; left: 0 — the whole window */
}top: 0 after scrolling 1500px; .overlay covers the window before and after scrolling.Flexbox and Grid: properties
Flexbox lays items out along one axis, Grid along rows and columns at once. The Flexbox and Grid lesson has a full walkthrough with practice; here you only get “property → what it does”.
display: flex
- Set on
- container
- What it does
- turns direct children into flex items
flex-direction
- Set on
- container
- What it does
- main axis:
row(default),column,row-reverse,column-reverse
flex-wrap
- Set on
- container
- What it does
nowrap(default) or wrapping onto new lines withwrap
justify-content
- Set on
- container
- What it does
- distribution along the main axis:
flex-start,center,space-between…
align-items
- Set on
- container
- What it does
- alignment on the cross axis;
stretchby default
align-content
- Set on
- container
- What it does
- distribution of lines when wrapping
gap
- Set on
- container
- What it does
- space between items
flex
- Set on
- item
- What it does
flex-growflex-shrinkflex-basis:1=1 1 0%,auto=1 1 auto,none=0 0 auto, noflex—0 1 auto
align-self
- Set on
- item
- What it does
- its own cross-axis alignment
order
- Set on
- item
- What it does
- changes only the visual order;
Taband screen readers follow the HTML
margin-left: auto
- Set on
- item
- What it does
- takes the free space and pushes the item right
.toolbar {
display: flex;
align-items: center; /* on the cross axis */
gap: 12px;
flex-wrap: wrap;
}
.toolbar .push { margin-left: auto; } /* push this item and the next ones right */
.columns { display: flex; gap: 16px; }
.columns > * {
flex: 1; /* = 1 1 0%: equal shares */
min-width: 0; /* long content does not stretch the column */
}min-width: 0 the second column grows past 3000px.grid-template-columns / -rows
- Set on
- container
- What it does
- tracks:
200px 1fr,repeat(3, 1fr)
repeat(auto-fit, minmax(240px, 1fr))
- Set on
- container
- What it does
- as many columns as fit;
auto-fitstretches them,auto-fillkeeps empty tracks
minmax(0, 1fr)
- Set on
- container
- What it does
- a column never exceeds its share, even with long content
grid-template-areas
- Set on
- container
- What it does
- a layout drawn with named areas
grid-auto-rows
- Set on
- container
- What it does
- height of implicitly created rows
justify-items / align-items / place-items
- Set on
- container
- What it does
- alignment of items inside their cells
grid-area
- Set on
- item
- What it does
- an area name from
grid-template-areas
grid-column: 1 / -1
- Set on
- item
- What it does
- from the first line to the last — full width
grid-column: span 2
- Set on
- item
- What it does
- take two columns
grid-template-columns: subgrid
- Set on
- nested grid
- What it does
- reuse the parent grid’s columns
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr));
gap: 16px;
}
.page {
display: grid;
grid-template-columns: 240px minmax(0, 1fr);
grid-template-areas:
"head head"
"side main"
"foot foot";
gap: 16px;
}
.page > header { grid-area: head; }
.page > aside { grid-area: side; }
.page > main { grid-area: main; }
.page > footer { grid-area: foot; }
.wide { grid-column: 1 / -1; } /* from the first line to the last */.page the sidebar is 240px and main 744px even with a long line, header and footer span the full width; .wide spans the whole row.Custom properties, media and container queries
Custom properties (“CSS variables”) start with --, are read with var() and are inherited by descendants. Override a variable on a component and everything inside that uses it changes.
:root {
--accent: #2563eb;
--space: 16px;
}
.btn {
background: var(--accent);
padding: var(--space);
color: var(--btn-text, white); /* white if --btn-text is not set */
}
.btn--danger {
--accent: #dc2626; /* the override applies inside .btn--danger */
}.btn has a #2563eb background and .btn--danger a #dc2626 one; the text colour is white from the fallback and black when an ancestor sets --btn-text..list { display: grid; gap: 16px; }
@media (width >= 768px) { /* same as (min-width: 768px) */
.list { grid-template-columns: 1fr 1fr; }
}
@media (prefers-reduced-motion: reduce) {
.fade { transition: none; }
}
.slot { container-type: inline-size; } /* a container for @container */
@container (width > 480px) {
.card { display: grid; grid-template-columns: 160px 1fr; }
}.list has one column at 767px and two at 768px; transition: none with prefers-reduced-motion: reduce; .card is a grid in a 600px container and a plain block in a 400px one.What it measures
@media- the browser window and device settings
@container- the size of the nearest container ancestor
What to declare
@media- nothing
@containercontainer-type: inline-size(orsize) on an ancestor
Use it for
@media- page layout, theme,
prefers-reduced-motion @container- a component that sits in a narrow column here and a wide one there
Range syntax
@media(width >= 768px)=(min-width: 768px)@container(width > 480px)
Modern features and their support
Baseline summarises support in Chrome, Edge, Firefox and Safari (desktop and mobile). “Widely available” means the feature has worked in all of them for at least 2.5 years; “Newly available” means only in the latest versions; “Limited” means not everywhere yet. The statuses below were taken on 12 October 2026 from MDN and the web-features 3.42 package; they change, so check the Baseline badge on the feature’s MDN page before you rely on it.
:is(), :where(), :focus-visible
- What it gives you
- short selector lists, zero weight, keyboard-only focus rings
- Baseline on 12 Oct 2026
- widely available
clamp(), min(), max()
- What it gives you
- sizing without media queries
- Baseline on 12 Oct 2026
- widely available
aspect-ratio, gap in Flexbox
- What it gives you
- box proportions, spacing without
margin - Baseline on 12 Oct 2026
- widely available
dvh, svh, lvh
- What it gives you
- window height aware of browser toolbars
- Baseline on 12 Oct 2026
- widely available
@layer
- What it gives you
- a controlled cascade order
- Baseline on 12 Oct 2026
- widely available
(width >= 768px) syntax
- What it gives you
- ranges in media queries
- Baseline on 12 Oct 2026
- widely available
@container (size)
- What it gives you
- component-level responsiveness
- Baseline on 12 Oct 2026
- widely available
color-mix()
- What it gives you
- mixing colours in CSS
- Baseline on 12 Oct 2026
- widely available
subgrid
- What it gives you
- a nested grid on the parent’s lines
- Baseline on 12 Oct 2026
- widely available
:has()
- What it gives you
- selecting a parent or a previous element
- Baseline on 12 Oct 2026
- widely available
& nesting
- What it gives you
- nested rules without a preprocessor
- Baseline on 12 Oct 2026
- widely available
text-wrap: balance
- What it gives you
- even heading lines
- Baseline on 12 Oct 2026
- newly available
@starting-style, light-dark()
- What it gives you
- entry animations, colours for light and dark themes
- Baseline on 12 Oct 2026
- newly available
@scope, field-sizing
- What it gives you
- scoped styles, a field sized to its text
- Baseline on 12 Oct 2026
- newly available
Container style queries
- What it gives you
@container style(--x: y)- Baseline on 12 Oct 2026
- newly available
Scroll-driven animations (animation-timeline)
- What it gives you
- animation tied to scrolling
- Baseline on 12 Oct 2026
- limited
/* Nesting without a preprocessor */
.card {
padding: 16px;
& .title { font-size: 1.25rem; }
&:hover { outline: 2px solid; }
}
/* Cascade layers: a later layer wins, specificity does not matter */
@layer reset, components;
@layer reset { #app button { margin: 0; } } /* 1-0-1 */
@layer components { .btn { margin: 8px; } } /* 0-1-0 — wins */
/* A branch only for browsers that support :has() */
@supports selector(:has(a)) {
.card:has(img) { display: grid; grid-template-columns: 120px 1fr; }
}.title is 20px through nesting, the button gets margin: 8px from the later layer even though #app button is heavier; the @supports branch applied.Centering and common pitfalls
/* 1. Grid: both axes in one line */
.center-grid {
display: grid;
place-items: center;
}
/* 2. Flex: the same, and handy for several items in a row */
.center-flex {
display: flex;
justify-content: center;
align-items: center;
}
/* 3. A page container, horizontally */
.container {
max-width: 1200px;
margin-inline: auto;
}
/* 4. On top of the parent (modal, badge) */
.center-abs-parent { position: relative; }
.center-abs {
position: absolute;
top: 50%;
left: 50%;
translate: -50% -50%;
}.container sits 40px from the edge of a 1280px window.one box on both axes
- Recipe
display: grid; place-items: center
several items in a row
- Recipe
display: flex; justify-content: center; align-items: center
a page container
- Recipe
max-width+margin-inline: auto
inline text and buttons inside a box
- Recipe
text-align: center
on top of the content
- Recipe
position: absolute; top: 50%; left: 50%; translate: -50% -50%
top and z-index do nothing
- Cause
- the element is
position: static - Fix
position: relative
sticky does not stick
- Cause
- no
top, or an ancestor hasoverflow: hidden - Fix
- set
top, removeoverflowfrom the ancestor
z-index: 9999 is under a sibling
- Cause
- the element is in another stacking context (its parent has its own
z-index) - Fix
- raise the parent, not the child
fixed is not relative to the window
- Cause
- an ancestor has
transform,filterortranslate - Fix
- move the element out of that ancestor
width: 100% + padding overflows
- Cause
content-box- Fix
box-sizing: border-box
a long word or code stretches a column
- Cause
- flex and grid items have a content-based minimum width
- Fix
min-width: 0orminmax(0, 1fr)
100vh is taller than the phone screen
- Cause
vhuses the large window height- Fix
100dvhor100svh
@container does not fire
- Cause
- no ancestor has
container-type - Fix
container-type: inline-size
Tailwind CSS has ready-made utilities for all of this — grid place-items-center, mx-auto, min-w-0, @container — see the Tailwind CSS lesson.
How this was verified4 · passedchecked 12 October 2026
“Run” means the code was executed in a scratch project or container and the output in the article comes from that run. “Partial” means it was checked against stubs instead of external services. “Not run” means it needs a device, an account or a key; the steps were checked against the official documentation. The verification project files and run logs are kept in the InterLAB archive.