CSS Highlights – 2022


JUMP TO:


1. Container Queries


Container queries let a component adapt its styles based on the size of its container, not the viewport.

They solve the problem media queries never could.


Why they exist

Media queries answer:

“How big is the screen?”

Container queries answer:

“How much space does this component have?”

This is essential for:

  • reusable components
  • cards in grids
  • sidebars vs main content
  • CMS layouts (very relevant to WordPress)

Minimal example

.card {
  container-type: inline-size;
}

@container (min-width: 400px) {
  .card {
    display: flex;
    gap: 1rem;
  }
}

If .card is wider than 400px, it changes layout — regardless of screen size.


Two steps you must remember

1️⃣ Declare a container

container-type: inline-size;

This opts the element into being a queryable container.


2️⃣ Query the container

@container (min-width: 400px) { ... }

Not @media.


Container types (quick)

  • inline-size → respond to width (most common)
  • size → respond to both width and height

Naming containers (optional but useful)

.card {
  container-type: inline-size;
  container-name: card;
}

@container card (min-width: 400px) {
  ...
}

Prevents accidental matches.


What container queries replace

Before:

  • viewport-based hacks
  • duplicated components
  • JS resize observers

Now:

  • components are truly portable
  • CSS stays declarative

2. Color Spaces and Functions


Why new CSS color spaces were added

Traditional CSS colors (rgb()hexhsl()) are based on sRGB, which:

  • was designed for old monitors
  • can’t represent many modern display colors
  • interpolates colors poorly (muddy gradients)

Modern CSS color features solve this by:

  • supporting wider gamuts
  • using perceptually uniform color spaces
  • giving better control and math

The big new color spaces

1️⃣ oklch() (most important)

color: oklch(60% 0.15 240);

What it is

  • A perceptually uniform color space
  • Based on how humans actually see color

Components

  • L → lightness (0–100%)
  • C → chroma (color intensity)
  • H → hue (angle)

Why it matters

  • Changing lightness doesn’t shift hue
  • Gradients look natural
  • Ideal for design systems

👉 This is the future default choice


2️⃣ oklab()

color: oklab(0.6 -0.1 -0.05);
  • Same family as oklch
  • Cartesian version (less intuitive)
  • Mostly used internally or for math

Most people prefer oklch().


3️⃣ lab() / lch()

color: lch(65% 40 130);
  • Older perceptual color spaces
  • Still better than hsl()
  • Slightly less accurate than OKLCH

Think of these as stepping stones.


4️⃣ Display color spaces (color())

color: color(display-p3 1 0 0);

Supported spaces:

  • display-p3
  • rec2020
  • a98-rgb
  • prophoto-rgb

These unlock more vivid colors on modern screens (Apple devices especially).


New & updated color functions

1️⃣ Modern rgb() and hsl() syntax

color: rgb(255 0 0 / 50%);
color: hsl(0 100% 50% / 0.5);
  • Spaces instead of commas
  • Built-in alpha channel
  • Matches other color functions

2️⃣ color-mix() (very useful)

background: color-mix(in oklch, red 70%, white);

Mixes colors correctly, in a chosen color space.

Better than manual rgba tricks.


3️⃣ Relative colors (powerful)

--brand: oklch(60% 0.2 250);

color: oklch(from var(--brand) calc(l + 10%) c h);

This means:

“Use the same color, but lighter”

Perfect for:

  • hover states
  • borders
  • themes

4️⃣ color-contrast() (experimental)

color: color-contrast(white vs black, navy);

Chooses the best contrast color automatically.

Accessibility-focused, but still evolving.


Gamut awareness (important concept)

color: oklch(70% 0.3 30);

If the color:

  • exists on the display → shown
  • doesn’t exist → browser clips safely

This avoids broken colors.


When you should care

Use new color features if you:

  • build design systems
  • care about accessibility
  • want consistent hover / active states
  • design on modern displays
  • want better gradients

You don’t need them for:

  • quick one-off sites
  • legacy browser support

Simple mental model

Old worldNew world
hexoklch()
rgba()color-mix()
hsl()perceptual lightness
guessworkmath-friendly colors

Example: modern button (clean & future-proof)

:root {
  --brand: oklch(60% 0.18 250);
}

button {
  background: var(--brand);
}

button:hover {
  background: color-mix(in oklch, var(--brand) 85%, black);
}

Predictable. Accessible. Maintainable.



3. :has() pseudo selector


:has() lets you style an element based on what it contains or what comes after it — effectively a “parent selector” in CSS.


Example:

.card:has(img) {
  border: 2px solid green;
}

Means:

“Style .card if it contains an <img>.”


Common, practical uses

Style a parent when a child exists

form:has(input:invalid) {
  border: 2px solid red;
}

Style based on sibling state

label:has(+ input:checked) {
  font-weight: bold;
}

Layout adjustments

.article:has(.sidebar) {
  grid-template-columns: 3fr 1fr;
}

Why it’s a big deal

Before :has():

  • required JavaScript
  • messy extra classes
  • duplicated markup

Now:

  • pure CSS
  • declarative
  • readable

Important rules

  • :has() is relational (looks inside or forward)
  • Cannot look up beyond the selected element
  • Can be performance-heavy if overused

4. New viewport units


The new viewport units (svhlvhdvh) fix mobile vh bugs by defining viewport sizes that account for dynamic browser UI.

The problem with old viewport units

Traditional units:

  • vw → 1% of viewport width
  • vh → 1% of viewport height

On mobile, vh was unreliable because:

  • browser address bars appear/disappear
  • the “viewport” height changes while scrolling
  • 100vh could be taller than the visible screen

Result:

  • content cut off
  • unwanted scrolling
  • broken full-height layouts

The new viewport units

CSS now defines three viewport concepts:

Unit typeWhat it represents
Large (lvhlvw)Maximum possible viewport size
Small (svhsvw)Minimum possible viewport size
Dynamic (dvhdvw)Current visible viewport size

1️⃣ svh — small viewport height

height: 100svh;
  • Uses the smallest viewport
  • Safe from content being hidden
  • Best for layouts that must always fit

✅ Good for:

  • full-screen forms
  • login pages
  • modals

2️⃣ lvh — large viewport height

height: 100lvh;
  • Uses the largest possible viewport
  • Can extend behind browser UI
  • Matches old 100vh behavior

⚠️ Rarely what you want now


3️⃣ dvh — dynamic viewport height (most useful)

min-height: 100dvh;
  • Updates as the UI changes
  • Tracks visible screen size
  • Smoothly adapts on scroll

✅ Best for:

  • hero sections
  • full-height layouts
  • modern responsive design

Safe modern pattern

.hero {
  min-height: 100svh;
  min-height: 100dvh;
}

Browsers that support dvh use it.
Others fall back to svh.


Width units too

Same idea applies to width:

  • svw
  • lvw
  • dvw

Less commonly needed, but useful for:

  • side panels
  • off-canvas menus

When you should switch

Replace this:

height: 100vh;

With one of:

  • 100dvh → flexible layouts
  • 100svh → strict fit layouts

5. accent-color


accent-color lets you set the color of built-in form controls (checkboxes, radio buttons, range sliders, progress bars) without fully restyling them.


What it affects

accent-color applies to native UI parts such as:

  • checkboxes
  • radio buttons
  • <input type="range">
  • <progress>

It does not affect:

  • text inputs
  • buttons
  • selects (beyond limited UA styling)

Minimal example

:root {
  accent-color: #0066cc;
}

All supported form controls now use that color.


Per-element control

input[type="checkbox"] {
  accent-color: green;
}

Why it’s useful

Before accent-color:

  • full custom controls required
  • lots of CSS
  • accessibility pitfalls

With accent-color:

  • native behavior preserved
  • keyboard & screen-reader support stays intact
  • theming is trivial

Works well with modern color features

:root {
  accent-color: oklch(60% 0.18 250);
}

Matches your design system colors cleanly.


Best practices

  • Prefer subtle, accessible colors
  • Ensure sufficient contrast
  • Use it as a theme hint, not full customization

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *