Lesson 21 of 30
Every lesson in this track so far has ended with the same honest caveat: check support, have a fallback. @supports is the native mechanism for actually writing that fallback instead of just talking about it.
11 min read
Every option explains itself — why the right one is right, and why each wrong one is wrong. Miss one and it comes back later on a spaced-repetition schedule.
Question 1 of 3
You want to apply a rule only in browsers that support the `:has()` selector. Which is the correct `@supports` syntax?
Hints without spoilers, and a plain explanation of why a wrong answer was wrong — answered only from this lesson. Free account, no card.
Sign up freeWhat interviewers actually ask about this, and what a strong answer has to cover.
A strong answer covers
Likely follow-ups
Once you're using container queries, :has(), anchor positioning, view transitions, or popover — everything else in this track — you run into the same question for each of them: what happens in a browser that doesn't understand this yet? The old answer was guesswork: sniff the User-Agent string, or bake a caniuse snapshot into a build tool and hope it stays current. Both approaches test the wrong thing — a browser version, or a build-time snapshot — instead of the thing that actually matters, which is whether this specific browser, right now understands this specific CSS.
@supports tests that directly. `@supports (container-type: inline-size) { ... }` asks the browser one question — "do you parse this property and value as valid?" — and only applies the enclosed rules if the answer is yes. It's a conditional block, evaluated by the same engine that's about to render the page, with zero guessing involved.
Two forms matter beyond the basic property-value check:
`@supports selector(...)` — property-value pairs aren't the only thing that can go unsupported; a whole selector can too. :has() is a selector, not a property, so testing for it is `@supports selector(:has(a)) { ... }`, not a property-value pair.`@supports not (...)` — the inverse, for writing the fallback path explicitly rather than just letting unsupported browsers silently miss the enhanced rule./* Baseline: works in every browser, no query support required */
.card {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
@supports (container-type: inline-size) {
.card-container {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
align-items: center;
}
}
}
@supports not (container-type: inline-size) {
/* An explicit fallback, not just "do nothing" */
.card {
border-inline-start: 3px solid var(--color-accent);
}
}
The baseline rule is unconditional — every browser gets a working stacked card. The @supports (container-type: inline-size) block layers the horizontal layout on top, only where the browser can actually honor it. The @supports not block is the part people skip: instead of leaving unsupported browsers with nothing extra, it gives them a small, deliberate visual cue (an accent border) so "no container query support" is a designed state, not an accident. The same pattern applies to every other feature in this track — `@supports selector(:has(a))` before relying on :has(), `@supports (anchor-name: --trigger)` before native anchor positioning, `@supports (position-area: top)` for the newer anchor-positioning properties specifically.
You can combine conditions the way you'd combine boolean expressions anywhere else: `@supports (display: grid) and (gap: 1rem)` requires both, `@supports (display: flex) or (display: -webkit-box)` requires either. This is how you gate a rule that depends on more than one feature landing together, or offer two acceptable code paths instead of one.
@supports proves the browser can parse a declaration, not that it behaves correctly. A browser can pass `@supports (anchor-name: --trigger)` and still have a real bug in some edge case of anchor positioning — feature detection tells you the syntax landed, not that every behavior is bug-free. Treat a passing @supports check as "safe to attempt," not "guaranteed correct."`@supports (display: grid)` is a valid check; a bare property name with no value isn't how the syntax works — you're always asking "does this browser accept this exact declaration."selector() and a plain property-value check test different things. Forgetting that :has() needs `@supports selector(:has(a))` and reaching for a property-value check instead is a common mistake, since the two forms look similar but query different parts of the CSS engine.@supports block with no fallback still ships a broken experience to unsupported browsers — it just fails silently instead of loudly. Pairing every enhancement block with either a baseline rule outside it or an explicit @supports not block is what makes progressive enhancement actually progressive, rather than "works great, or doesn't work at all, with no way to tell which from the outside.""How would you ship a CSS feature you're not sure has full browser support?" is really asking whether a candidate reaches for @supports and a designed fallback path, or for browser sniffing and a caniuse table baked into the build — the former is the answer that scales to features nobody's written a support table for yet.