w3tweaks.com · CSS Tutorial

CSS @supports

Feature detection · Progressive enhancement · Live browser checker

Tab 1

Live Browser Support Checker

This table is generated by actually running @supports queries in your current browser — not from a static compatibility chart. Results reflect what this specific browser instance supports right now.

Pure CSS @supports Proof — Visible Only If Supported
display: grid
✓ Supported
clip-path
✓ Supported
backdrop-filter
✓ Supported
:has() selector
Supported
container queries
Supported
Why this matters: The icons above are rendered using only @supports CSS rules — no JavaScript. Each element is display: none by default and set to visible only if the browser actually supports the feature. This is progressive enhancement: the base experience works everywhere; the enhanced experience activates only where it can.
Tab 2

@supports Query Builder

Select a query type and feature. See the generated syntax, whether your browser passes, and a live visual of what it unlocks.

Generated query checking...
@supports (display: grid) {
  /* styles applied only if supported */
}
What it unlocks
With img — highlighted
Without img — default
Fallback: base CSS shown when feature not supported
@supports selector() — test selectors
/* Is :has() supported? */
@supports selector(:has(*)) {
  .card:has(img) { border: 2px solid orange; }
}

/* Is :nth-child(n of sel) supported? */
@supports selector(:nth-child(1 of .a)) {
  .item:nth-child(2 of .active) { ... }
}

Tests if a CSS selector is valid and supported — not just a property.

@supports at-rule() — Chrome 148+ New
/* Is @layer supported? */
@supports at-rule(@layer) {
  @layer base, components, utils;
}

/* Is @scope supported? */
@supports at-rule(@scope) {
  @scope (.card) { ... }
}

Detects if a CSS at-rule itself is supported — previously impossible cleanly.

Tab 3

5 Real Progressive Enhancement Patterns

Each pattern shows the base (universal) CSS first, then the @supports enhancement layered on top. Old browsers get a working experience; modern browsers get the enhanced one.

📐 Grid → Flexbox Fallback
/* Base: flexbox everywhere */
.gallery {
  display: flex;
  flex-wrap: wrap; gap: 1rem;
}
.gallery-item { flex: 1 1 200px; }

/* Enhanced: modern grid */
@supports (display: grid) {
  .gallery { display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
  .gallery-item { flex: unset; }
}

Flex provides a working layout everywhere. Grid enhancement gives perfect auto-fill columns in modern browsers.

🧊 backdrop-filter + or Operator
/* Base: opaque fallback */
.nav { background: rgba(15,23,42, 0.95); }

/* Enhanced: frosted glass */
@supports
  (backdrop-filter: blur(1px)) or
  (-webkit-backdrop-filter: blur(1px)) {
  .nav {
    background: rgba(15,23,42, 0.5);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
  }
}

The or operator checks either the standard or WebKit-prefixed version. The frosted glass only activates when supported — older browsers get a clean opaque nav.

🎯 :has() Progressive Enhancement
/* Base: all cards look the same */
.card { border: 1px solid #e2e8f0; }

/* Enhanced: card reacts to content */
@supports selector(:has(*)) {
  .card:has(img) {
    border-color: orange;
    box-shadow: 0 0 0 2px rgba(249,115,22,.2);
  }
  label:has(input:checked) { background: lightgreen; }
}

Uses selector(:has(*)) — not just (:has(*)). Wrapping in selector() correctly tests the selector syntax, not just the :has property name.

📚 @layer + @supports at-rule()
/* Base: flat CSS everywhere */
.btn { background: blue; color: white; }

/* Enhanced: structured layers (Chrome 148+) */
@supports at-rule(@layer) {
  @layer base, components, utils;
  @layer components {
    .btn { background: blue; }
  }
}

The new at-rule() function (Chrome 148+) detects if @layer, @scope, @property etc. are supported. Cleanly gates the entire layered architecture.

📦 @import supports() — Conditional Loading
/* Only load grid.css when grid is supported */
@import "grid-layout.css"
  supports(display: grid);

/* Load mask file only if mask-image works */
@import "masks.css"
  supports((mask-image: linear-gradient(black,black)) or
  (-webkit-mask-image: linear-gradient(black,black)));

Apply @supports directly on @import — the stylesheet is only fetched when the condition passes. Reduces network requests on unsupported browsers.

⚙️ CSS.supports() — JavaScript API
/* Two forms — both valid */

// Form 1: two arguments
CSS.supports('display', 'grid'); // true/false

// Form 2: single condition string
CSS.supports('(display: grid)');
CSS.supports('selector(:has(*))');
CSS.supports('not (display: grid)');

// Real usage
if (CSS.supports('selector(:has(*))')) {
  // load :has()-dependent JS behavior
}

The JavaScript mirror of @supports. Form 2 (single string) accepts the full condition syntax including selector(), not, and, or.

@when/@else — the future of feature queries: A proposed syntax (@when / @else) would replace the verbose dual-block @supports X { } @supports not X { } pattern with a cleaner if/else structure. Not yet implemented in any browser as of 2026, but approved by the CSSWG. Worth watching.
Read the tutorial