Skip to main content

CSS Feature Queries: @supports for Progressive Enhancement

@supports lets you apply CSS only if the browser supports a specific property or value. It's the CSS equivalent of JavaScript feature detection — write better CSS for modern browsers while gracefully falling back for older ones.


1. The Basic Syntax

/* @supports (property: value) { ... } */

/* Apply styles ONLY if backdrop-filter is supported */
@supports (backdrop-filter: blur(1px)) {
.c-glass {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
}

/* Without @supports: fallback for browsers that don't support backdrop-filter */
.c-glass {
background: rgba(30, 30, 40, 0.95); /* Opaque fallback — always applied first */
}

@supports (backdrop-filter: blur(1px)) {
.c-glass {
background: rgba(255, 255, 255, 0.08); /* Transparent enhancement */
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
}

2. The Pattern: Fallback First

/* Step 1: Write the fallback — works everywhere */
.c-layout {
display: block; /* Fallback: block layout */
}
.c-layout > * {
width: 33.333%;
float: left; /* Old float-based approximation */
}

/* Step 2: Enhance for supporting browsers */
@supports (display: grid) {
.c-layout {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.5rem;
}
.c-layout > * {
width: auto; /* Reset float-era sizing */
float: none;
}
}

3. Logical Operators

/* NOT: apply when feature is NOT supported */
@supports not (display: grid) {
.c-layout > * { width: 33.333%; float: left; }
}

/* AND: both conditions must be true */
@supports (display: grid) and (gap: 1rem) {
.c-grid { display: grid; gap: 1.5rem; }
}

/* OR: either condition can be true */
@supports (transform: translateX(0)) or (-webkit-transform: translateX(0)) {
.c-slide { transform: translateX(-100%); }
}

/* Nesting @supports for complex conditions */
@supports (display: grid) {
@supports (grid-template-rows: subgrid) {
/* Both grid AND subgrid supported */
.c-card {
grid-template-rows: subgrid;
grid-row: span 4;
}
}
}

4. @supports selector() — Selector Feature Detection

Test whether a browser supports a specific selector:

/* :has() support — the parent selector */
@supports selector(:has(*)) {
/* :has() is supported */
.c-form__group:has(input:invalid) {
border-color: var(--color-danger);
}
.c-card:has(> img:first-child) {
padding-top: 0;
}
}

/* :is() support */
@supports selector(:is(h1)) {
:is(h1, h2, h3) { line-height: 1.2; }
}

/* Without @supports: browsers that don't understand :has() simply skip it */
/* (Unlike @supports, many modern CSS features degrade gracefully on their own) */
/* Use @supports selector() when you want to provide an EXPLICIT fallback */

5. Common @supports Patterns in 2026

CSS Grid Subgrid

/* Fallback: flexbox card layout (no cross-card alignment) */
.c-card {
display: flex;
flex-direction: column;
}
.c-card__body { flex: 1; } /* Push button to bottom */

/* Enhancement: subgrid alignment across cards */
@supports (grid-template-rows: subgrid) {
.c-card {
display: grid;
grid-template-rows: subgrid;
grid-row: span 4;
}
.c-card__body { flex: unset; } /* Remove flex override */
}

backdrop-filter (Glassmorphism)

/* Fallback: semi-opaque solid */
.c-nav {
background: rgba(10, 10, 15, 0.95);
}

@supports (backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)) {
.c-nav {
background: rgba(10, 10, 15, 0.5);
-webkit-backdrop-filter: saturate(180%) blur(20px);
backdrop-filter: saturate(180%) blur(20px);
}
}

Container Queries

/* Fallback: media query based layout */
@media (min-width: 768px) {
.c-card { flex-direction: row; }
}

/* Enhancement: container query based layout */
@supports (container-type: inline-size) {
.c-card-wrapper { container-type: inline-size; }

@container (min-width: 500px) {
.c-card { flex-direction: row; }
}

/* Now remove the media query if it's redundant */
}

CSS Nesting

/* @supports for nesting — check browser support if needed */
@supports (selector(&)) {
/* Browser supports CSS nesting */
.c-btn {
&:hover { transform: translateY(-2px); }
&--primary { background: var(--color-primary); }
}
}

OKLCH Colors

/* Fallback: hex/hsl colors */
:root {
--color-primary: #6c63ff;
--color-primary-dark: #5b52e6;
}

/* Enhancement: perceptually uniform OKLCH */
@supports (color: oklch(0 0 0)) {
:root {
--color-primary: oklch(60% 0.18 290);
--color-primary-dark: oklch(52% 0.20 290);
}
}

CSS color-mix()

.c-btn--secondary { background: #5b52e6; }

@supports (background: color-mix(in srgb, red 50%, blue 50%)) {
.c-btn--secondary {
background: color-mix(in oklch, var(--color-primary) 30%, transparent);
}
}

6. When NOT to Use @supports

/* ❌ Unnecessary: well-supported properties don't need @supports */
@supports (display: flex) {
.c-nav { display: flex; } /* Flex is 99%+ supported — just use it */
}

/* ❌ Unnecessary: @supports doesn't help with value differences */
@supports (margin: 1rem) { /* This is meaningless — margin is 100% supported */
margin: 1rem;
}

/* ✅ Use @supports for: */
/* - Properties with <85% support (backdrop-filter, container queries, subgrid) */
/* - Experimental or newer features you want to progressively enhance */
/* - When you need explicit fallback CSS (not just "skip if unsupported") */

/* ✅ Often don't need @supports if: */
/* - Unsupported browsers just see nothing (no visual artifact) */
/* - Feature degrades silently and acceptably */

7. CSS Property + Value Testing

Some properties only need value-level testing:

/* Testing for a specific VALUE, not just the property */
@supports (width: fit-content) {
.c-tag { width: fit-content; }
}

/* Or use the min() function */
@supports (width: min(100%, 600px)) {
.c-container { width: min(100% - 2rem, 1200px); }
}

/* Testing for clamp() */
@supports (font-size: clamp(1rem, 2vw, 2rem)) {
h1 { font-size: clamp(2rem, 5vw, 4rem); }
}

8. DevTools for @supports Debugging

Chrome DevTools:
Elements panel → Styles tab → @supports rules shown with condition label
If condition fails → rule shown in gray/strikethrough

Firefox DevTools:
Style Editor → @supports rules highlighted
Computed tab → shows which rules are actually applied

Testing in DevTools without changing browser:
1. Styles panel → find the @supports rule
2. Click the condition text → temporarily edit to @supports (max-width:0)
3. Rule now always fails → see the fallback styles
4. Undo to restore

9. @supports Quick Reference

TestSyntax
Property support@supports (display: grid)
Value support@supports (width: fit-content)
Selector support@supports selector(:has(*))
NOT@supports not (display: grid)
AND@supports (a: 1) and (b: 1)
OR@supports (a: 1) or (-webkit-a: 1)
CSS Nesting@supports selector(&)
Custom property@supports (--foo: bar) — always true

10. AI Prompt for Feature Queries

Use @supports for progressive enhancement:

For these features, write fallback + @supports enhancement:
1. backdrop-filter (glass nav) — fallback: rgba(10,10,15,0.95)
2. grid subgrid (card alignment) — fallback: flex-direction:column + flex:1 body
3. container queries (card responsiveness) — fallback: media query
4. OKLCH colors — fallback: hex values

Always structure:
1. Fallback CSS (no @supports wrapper)
2. @supports { enhanced CSS }
Never use @supports for widely supported properties (flex, grid, custom props)