How to buildOctober 12, 20269 min read
Developer ReferenceStep 4 of 8

CSS reference: selectors, units, Flexbox and Grid

In short

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.

ReferenceLevel: BeginnerCSS Reference

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 with clamp()
  • find the Flexbox or Grid property for the job and centre a box with one of the tested recipes

Verified with

Microsoft Edge 155 + Playwright 1.64web-features 3.42 (Baseline)

Checked against docs: October 12, 2026

Contents
  1. 01Selectors: from simple ones to :is(), :where() and :has()
  2. 02Specificity: how to calculate a selector’s weight
  3. 03Units and sizing functions
  4. 04Box model and positioning
  5. 05Flexbox and Grid: properties
  6. 06Custom properties, media and container queries
  7. 07Modern features and their support
  8. 08Centering and common pitfalls

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
B anywhere inside A
Example
nav a

A > B

What it selects
B as a direct child of A
Example
ul > li

A + B

What it selects
B right after A
Example
h2 + p

A ~ B

What it selects
every B after A on 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
A that contains or is followed by B
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:

has.css
/* 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;
}
Checked in Edge 155: 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 .menu matched

:where(#nav) li

Weight
0-0-1
Why
:where() is always 0

style="…"

Weight
above any selector
Why
only !important overrides it
specificity.css
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 */
All four rules match <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.
  • !important and 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; inside font-size — the parent’s
Good for
button padding, an icon next to text

%

Relative to
the containing block; padding and margin in % use its width
Good for
column widths, proportional spacing

vw, vh

Relative to
1% of the window width / height; vh equals lvh
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
units.css
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 */
Checked in Edge 155: the title is 24px at a 320px window, 36px at 800px and 48px at 1600px; 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.

box.css
*, *::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 */
}
Checked: .box is 300px wide with 256px for content; with content-box the same box is 344px.

static

In the flow?
yes
top/left relative to
they have no effect
Typical use
the default

relative

In the flow?
yes, its space is kept
top/left relative to
its normal place
Typical use
a reference point for absolute children

absolute

In the flow?
no
top/left relative to
the nearest ancestor whose position is not static
Typical use
a badge, a close button

fixed

In the flow?
no
top/left relative to
the window; an ancestor with transform/filter instead, if there is one
Typical use
a floating button, an overlay

sticky

In the flow?
yes
top/left relative to
the nearest scrolling ancestor; needs top or another inset
Typical use
a table header, a menu
position.css
.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 */
}
Checked in Edge 155: the badge sits 8px from the card’s top right corner; the header is at 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 with wrap

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; stretch by 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-grow flex-shrink flex-basis: 1 = 1 1 0%, auto = 1 1 auto, none = 0 0 auto, no flex — 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; Tab and screen readers follow the HTML

margin-left: auto

Set on
item
What it does
takes the free space and pushes the item right
Flexbox
flex.css
.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 */
}
Checked: the last button is at the toolbar’s right edge and items are vertically centred; three 200px columns in a 632px container even though the second one holds a 400-character line. Without 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-fit stretches them, auto-fill keeps 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
Grid
grid.css
.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 */
Checked: 1 / 3 / 4 cards per row at 375 / 800 / 1280px windows; in a 1000px .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.

vars.css
: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 */
}
Checked: .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.
queries.css
.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; }
}
Checked: .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
@container
container-type: inline-size (or size) 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
modern.css
/* 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; }
}
Checked in Edge 155: .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

center.css
/* 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%;
}
Checked: in all three variants the child’s centre matches the parent’s (0px offset); a 1200px .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 has overflow: hidden
Fix
set top, remove overflow from 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, filter or translate
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: 0 or minmax(0, 1fr)

100vh is taller than the phone screen

Cause
vh uses the large window height
Fix
100dvh or 100svh

@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.

Tell us about your task — we'll review your request and get back to you

Got a project?
We'll scope and estimate it — for free.

Tell us about the product or the problem. After reviewing the task we'll come back with an architecture, a timeline and a price for the first version.

  • Scope and risks estimated before we start
  • NDA — yours or ours
  • Work starts within a week

Please do not send passwords, access keys or source code through this form — we will agree a safe way to share access separately.

By clicking the button you agree to the processing of your personal data.