ESC

Type to search the knowledge base.

@supports Feature Queries

Progressively enhance with @supports — detect properties, selectors, and font tech, and structure fallbacks that still look intentional.

intermediate3 min read
  • css
  • supports
  • feature-queries

@supports is a feature query: apply CSS only when the engine claims to support a given property/value, selector, or condition. It is the CSS cousin of capability detection — better than UA sniffing for progressive enhancement.

Docs: MDN @supports, feature queries guide.

Basic property checks

.card {
  background: #f4f0ea; /* fallback */
}

@supports (background: color-mix(in srgb, red, blue)) {
  .card {
    background: color-mix(in srgb, var(--brand) 12%, white);
  }
}
@supports (backdrop-filter: blur(4px)) {
  .topbar {
    background: rgb(255 255 255 / 0.7);
    backdrop-filter: blur(12px);
  }
}

Selector support

@supports selector(:has(*)) {
  .card:has(img) {
    padding: 0;
  }
}

Useful for :has(), nesting-related selectors, and other newer selector features.

Logical operators

@supports (display: grid) and (gap: 1rem) {
  .layout {
    display: grid;
    gap: 1rem;
  }
}

@supports not (display: grid) {
  .layout {
    display: flex;
    flex-wrap: wrap;
  }
}

@supports (display: grid) or (display: -ms-grid) {
  /* historical patterns; prefer modern checks */
}

Fonts and tech

@supports (font-variation-settings: normal) {
  body {
    font-family: "InterVariable", system-ui, sans-serif;
    font-optical-sizing: auto;
  }
}

@supports (font-tech(color-COLRv1)) {
  /* color font enhancements */
}

Structure fallbacks intentionally

Order matters: baseline first, enhancement inside @supports.

.grid {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

.grid > * {
  flex: 1 1 16rem;
}

@supports (grid-template-columns: subgrid) {
  /* optional advanced path */
}

Don’t leave unsupported browsers with display: grid tracks that break because you only set modern values.

@supports vs @media

At-rule Question
@media What’s the environment (width, preference, print)?
@supports What can this CSS engine parse/apply?

They nest freely:

@media (min-width: 48rem) {
  @supports (display: grid) {
    .page {
      display: grid;
      grid-template-columns: 14rem 1fr;
    }
  }
}

Interview out-loud

“@supports applies rules when a feature is supported — properties, values, or selectors. I write a solid baseline first, then enhance inside feature queries. I use not/and/or for compound checks and I don’t confuse feature queries with media queries.”

Footguns

  1. Checking a property that all target browsers already support — noise.
  2. False confidence: @supports can pass while a sub-feature is buggy.
  3. Empty baseline relying only on supported path.
  4. Detecting with JS when CSS @supports would suffice for pure style forks.
  5. Over-nesting until cascade ownership is unclear.

Pairing with @media and @layer

@layer components {
  .panel {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }
  @supports (grid-template-rows: subgrid) {
    @media (min-width: 48rem) {
      .panel {
        display: grid;
        /* advanced path */
      }
    }
  }
}

Feature queries do not protect you from bugs inside a supported property. Still keep visual QA. For JS feature detection that must match CSS, consider dual checks carefully — CSS @supports and CSS.supports() should use the same condition string when possible.

False friends

A browser may claim support for display: grid while shipping incomplete subgrid, or support gap in flex only after a certain version. Feature queries are coarse. Structure enhancements so incomplete support still yields usable layout — not a broken halfway grid. When a bug is engine-specific, a targeted @supports plus a code comment with the bug link is kinder than user-agent sniffing.

Further reading

Related guides