Not every browser understands the same CSS properties at the same time. A newer feature like subgrid or backdrop-filter might work perfectly in a recent browser while being silently ignored in an older one, with no error message at all: the line is simply skipped. A feature query exists for exactly this situation: it asks the browser, in plain CSS, whether it understands a given property before you depend on it.
It differs from a Media query, which asks about the display environment, its size, its orientation: a feature query asks about the rendering engine itself. It is the tool that lets you offer a modern layout to browsers that support it while keeping a correct, simpler layout for the rest, with no JavaScript involved.
Definition
A feature query is a conditional CSS rule that starts with @supports and only applies the styles inside it when the browser recognizes the property and value being tested. Unlike a media query, which reacts to the display environment, a feature query reacts to the capabilities of the CSS engine itself. It lets you write a modern style with confidence, while providing a fallback for browsers that do not support it yet.
Basic syntax
The syntax reuses a property and a value, exactly as in a normal declaration, but wrapped in parentheses after @supports. If the test passes, the block that follows is applied normally, just like any other CSS rule.
@supports (display: grid) {
.container {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
}Outside that block, you usually write a fallback style that works everywhere. A browser that does not understand grid keeps using that fallback and never enters the @supports block at all.
Combining several conditions
@supports accepts the and, or, and not operators to build more precise conditions. This becomes useful when a feature depends on two properties at once, or when you want to specifically target browsers that do not support a novelty yet.
@supports (display: grid) and (gap: 1rem) {
.container {
display: grid;
gap: 1rem;
}
}
@supports not (aspect-ratio: 1 / 1) {
.thumbnail {
padding-top: 100%;
}
}not is especially handy for writing the fallback right inside its own @supports block, instead of leaving it outside any condition where it could accidentally be overwritten later.
Concrete example: layout with a fallback
Here is a common case: a card grid that uses CSS Grid in modern browsers, with a flexible column fallback for the rest.
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
.cards > * {
flex: 1 1 280px;
}
@supports (display: grid) {
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
}The flex style acts as a universal baseline. The @supports block then fully replaces it in browsers that understand grid, so no visitor ever ends up with a broken page.
Testing a value, not just a property
A common trap is assuming that @supports only checks a property name. In reality you should always test a property and value pair together, because a property can exist for years while gaining new values much later.
/* Wrong: position has existed forever */
@supports (position) {
.bar {
position: sticky;
}
}
/* Right: test the actual declaration you plan to use */
@supports (position: sticky) {
.bar {
position: sticky;
}
}Common use cases
Feature queries are mostly useful for recent or compatibility-sensitive properties: subgrid, grid itself in its early days, or costly effects like a background filter. They also document intent in the code: a developer reading the file immediately understands that a style is conditioned on a specific browser capability.
| Situation | Good habit |
|---|---|
| New, not-yet-widespread property | Fallback style outside @supports, enhancement inside it |
Two related properties (grid and gap, for instance) | Combine them with and in a single condition |
| Explicitly targeting an old browser | Use not to isolate that case |
FAQ
No, the test is evaluated once by the browser when the stylesheet loads, exactly like any other CSS rule: there is no noticeable performance cost in practice.
@supports test JavaScript or HTML features?No, @supports only tests CSS declarations. To detect a JavaScript API you need a script-side check, for example the CSS.supports() object, which mirrors the same logic in JavaScript.
@supports at all?Browsers old enough to ignore @supports also ignore the entire rule, including its content, so they naturally fall back to the style written outside the block, which makes this technique safe by construction.