CSS Highlights – 2021

JUMP TO:



1. aspect-ratio


aspect-ratio is a modern CSS property that lets you tell the browser “this box should keep this width-to-height proportion” — without hacks, wrappers, or JavaScript.


What aspect-ratio does

aspect-ratio: 16 / 9;

This means:

For every 16 units of width, keep 9 units of height.

The browser will calculate the missing dimension automatically.


The simplest example

<div class="box"></div>
.box {
  width: 300px;
  aspect-ratio: 16 / 9;
  background: lightgray;
}

Result:

  • width = 300px
  • height = automatically 168.75px

No padding-bottom tricks needed.


The key rule

aspect-ratio only matters when ONE dimension is missing

If both width and height are set, aspect ratio is ignored.

/* aspect-ratio is ignored */
.box {
  width: 300px;
  height: 200px;
  aspect-ratio: 16 / 9;
}

Common real-world use cases

1️⃣ Responsive images / videos

.video {
  aspect-ratio: 16 / 9;
}

.video iframe {
  width: 100%;
  height: 100%;
}

Used for:

  • YouTube / Vimeo
  • embedded maps
  • iframes

2️⃣ Image placeholders (no layout shift)

img {
  aspect-ratio: 3 / 2;
  width: 100%;
  object-fit: cover;
}

Prevents content jumping while images load (important for performance).


3️⃣ Cards with consistent shapes

.card-image {
  aspect-ratio: 1 / 1;
  background-size: cover;
}

Perfect for:

  • product grids
  • galleries
  • blog thumbnails

Aspect ratio with Grid & Flexbox

Grid example

.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1rem;
}

.item {
  aspect-ratio: 4 / 3;
  background: #ddd;
}

Each item:

  • auto-sizes height
  • stays proportional
  • fills the grid cell width

Aspect ratio on replaced elements (images, video)

Images already have an intrinsic aspect ratio.

img {
  width: 100%;
  height: auto;
}

But CSS aspect-ratio can override or enforce consistency:

img {
  aspect-ratio: 1 / 1;
  object-fit: cover;
}

aspect-ratio vs the old padding hack

Old way (avoid now)

.box {
  position: relative;
  padding-bottom: 56.25%; /* 16:9 */
}

.box iframe {
  position: absolute;
  inset: 0;
}

New way (preferred)

.box {
  aspect-ratio: 16 / 9;
}

Cleaner, readable, maintainable.


Browser support (important part)

Current support (modern reality)

  • ✅ Chrome
  • ✅ Edge
  • ✅ Firefox
  • ✅ Safari
  • ✅ iOS Safari
  • ✅ Android Chrome

Support is excellent in all modern browsers.

Older browsers

  • ❌ Internet Explorer
  • ⚠️ Very old mobile browsers

If you still support IE, you need the padding hack as a fallback.


Safe fallback pattern (progressive enhancement)

.box {
  height: 0;
  padding-bottom: 56.25%;
}

@supports (aspect-ratio: 1 / 1) {
  .box {
    height: auto;
    padding-bottom: 0;
    aspect-ratio: 16 / 9;
  }
}

Interaction with widthmax-widthmin-width

.box {
  width: 100%;
  max-width: 400px;
  aspect-ratio: 1 / 1;
}

The browser:

  1. Calculates width
  2. Derives height from aspect ratio
  3. Applies constraints

Mental model (easy to remember)

  • Width known → height is calculated
  • Height known → width is calculated
  • Both known → aspect-ratio ignored

2. Logical Properties


CSS logical properties may feel “optional” at first, but once they click, they make a lot of sense—especially for internationalisation and modern layouts.


The core idea (plain English)

Physical properties describe screen directions:

  • top / right / bottom / left

Logical properties describe content flow:

  • start / end
  • block / inline

So instead of saying:

“Put space on the top

you say:

“Put space at the start of the block direction


Block vs inline (most important concept)

In normal English text (default)

writing-mode: horizontal-tb;
direction: ltr;
  • Block direction → top → bottom
  • Inline direction → left → right

So:

LogicalPhysical equivalent
block-starttop
block-endbottom
inline-startleft
inline-endright

margin-block-start

.card {
  margin-block-start: 1rem;
}

This means:

“Add margin at the start of the block flow

In normal English text, this behaves exactly like:

margin-top: 1rem;

Why not margin-start?

Because start of what?

CSS separates two axes:

  • Block axis → paragraphs stack
  • Inline axis → text flows

So you must specify which axis:

  • margin-block-start
  • margin-inline-start

There is no plain margin-start.


Common logical property mappings

Margins & padding

PhysicalLogical
margin-topmargin-block-start
margin-bottommargin-block-end
margin-leftmargin-inline-start
margin-rightmargin-inline-end
padding-leftpadding-inline-start

Size & positioning

PhysicalLogical
widthinline-size
heightblock-size
topinset-block-start
leftinset-inline-start

Example:

.box {
  inline-size: 300px;
  block-size: 200px;
}

Why logical properties exist (the real reason)

They exist for internationalisation and writing modes.

Right-to-left languages (Arabic, Hebrew)

direction: rtl;
  • inline-start becomes right
  • inline-end becomes left

Your layout automatically flips — no extra CSS.


Vertical writing systems (Japanese, Chinese)

writing-mode: vertical-rl;

Now:

  • block direction → right → left
  • inline direction → top → bottom

Logical properties adapt.
Physical ones break.


Example: why logical beats physical

Bad (physical)

.card {
  margin-left: 1rem;
}

RTL users get spacing on the wrong side.


Good (logical)

.card {
  margin-inline-start: 1rem;
}

Correct for:

  • LTR
  • RTL
  • vertical text

Shorthand logical properties

margin-block: 1rem 2rem;
margin-inline: 0.5rem 1rem;

Equivalent to:

margin-top / bottom
margin-left / right

…but flow-aware.


Browser support as of 2026

  • ✅ Chrome
  • ✅ Edge
  • ✅ Firefox
  • ✅ Safari
  • ✅ Mobile browsers

Support is excellent for margins, padding, sizes, and insets.


Should you use them everywhere?

Use logical properties when:

  • building reusable components
  • supporting RTL languages
  • writing design systems
  • working with Grid / Flex layouts

Physical properties are still fine when:

  • doing one-off visual tweaks
  • working on legacy code
  • intentionally breaking symmetry

3. Cascade Layers


Cascade layers are a modern CSS feature that give you explicit control over the cascade itself — not just selectors and specificity.


What cascade layers are (in one sentence)

Cascade layers (@layer) let you define ordered “buckets” of CSS so some styles always win over others, regardless of selector specificity.


Why they exist (the problem they solve)

Traditionally, CSS priority is decided by:

  1. Origin (browser → user → author)
  2. Importance (!important)
  3. Specificity
  4. Source order

This breaks down when:

  • mixing framework CSStheme CSS, and custom overrides
  • specificity wars (.foo .bar div span)
  • using !important everywhere

Layers let you say:

“These styles are more important than those styles — full stop.”


The simplest example

@layer base {
  h1 {
    color: black;
  }
}

@layer overrides {
  h1 {
    color: blue;
  }
}

Even though both selectors are identical, overrides wins because it’s defined later in the layer order.


Declaring layer order (best practice)

@layer reset, base, components, utilities;

This sets the priority once, at the top of your CSS.

  • reset → lowest priority
  • utilities → highest priority

Putting CSS into layers

@layer reset {
  * {
    margin: 0;
  }
}

@layer base {
  body {
    font-family: system-ui, sans-serif;
  }
}

@layer components {
  .card {
    padding: 1rem;
  }
}

@layer utilities {
  .text-center {
    text-align: center;
  }
}

Key rule to remember (very important)

Layer order beats specificity.

This wins:

@layer utilities {
  h1 {
    color: red;
  }
}

Over this:

@layer base {
  h1.special.title {
    color: green;
  }
}

Even though the second selector is much more specific.


Where unlayered CSS fits

Unlayered CSS sits above all layers.

h1 {
  color: purple;
}

This will override any layered rule.

This is intentional and useful for quick overrides.


Nesting layers

@layer framework.components {
  .btn {
    padding: 1rem;
  }
}

Creates:

  • framework
  • framework.components

Order is still controlled at the top.


Layers vs !important

@layer!important
StructuralEmergency override
PredictableHard to reason about
Scales wellCauses conflicts
EncouragedAvoid when possible

4. content-visibility


content-visibility is a performance-focused CSS property that tells the browser whether it should bother rendering an element’s contents right now.

It’s powerful, but subtle.


What content-visibility does (plain English)

content-visibility: auto;

Means:

“If this element is off-screen, skip layout, paint, and rendering of its contents until it’s needed.”

This can dramatically improve:

  • initial page load
  • time to first render
  • scrolling performance

The three values you’ll actually use

visible (default)

content-visibility: visible;

Browser renders everything as normal.


hidden

content-visibility: hidden;
  • Contents are not rendered
  • Element still takes up space
  • Similar to visibility: hidden, but stronger

auto (the important one)

.content {
  content-visibility: auto;
}
  • Browser renders content only when near viewport
  • Automatically “turns on” when scrolled into view
  • No JavaScript required

Simple example

<section class="article">
  <h2>Article title</h2>
  <p>Lots of text…</p>
</section>
.article {
  content-visibility: auto;
}

If the section is far below the fold:

  • browser skips rendering it
  • page loads faster

Very important companion: contain-intrinsic-size

When content isn’t rendered yet, the browser needs to know how much space to reserve.

.article {
  content-visibility: auto;
  contain-intrinsic-size: 1000px;
}

This:

  • prevents layout jumps
  • gives the browser a placeholder size

You can also do:

contain-intrinsic-size: 800px 300px;

(height width)


Where content-visibility shines

1️⃣ Long pages

  • blog posts
  • documentation
  • product listings
  • event archives (very relevant to WordPress sites)
.post {
  content-visibility: auto;
  contain-intrinsic-size: 600px;
}

2️⃣ Repeated heavy components

  • image galleries
  • maps
  • embeds
  • sliders

3️⃣ Tab panels / accordions

.tab-panel {
  content-visibility: hidden;
}

Then switch to visible when active.


Where not to use it

🚫 Above-the-fold content
🚫 Small components
🚫 Elements that rely on size immediately (JS measurements)
🚫 Anything needing immediate accessibility focus


Interaction with JavaScript (important gotcha)

If JS queries layout info:

element.getBoundingClientRect();

And the element is not rendered yet:

  • values may be zero or delayed
  • measurements update only when visible

So:

Don’t use content-visibility on elements JS depends on immediately.


Accessibility notes

  • Screen readers can still access content
  • Focused elements force rendering
  • Safe when used for large, scroll-based content

Still, test with:

  • keyboard navigation
  • screen readers

Comments

Leave a Reply

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