Skip to content

CSS if(): Inline Conditionals Tested in Chromium, Firefox and WebKit

CSS if() picks a value from style(), media() or supports() tests inside one declaration. Probed in three engines: where it beats classes, and the traps.

· · 9 min read
A CSS if() declaration with two style() branches next to the result each of three browser engines gave

Quick Take

CSS if() chooses a value inside a single declaration by testing a custom property, a media query or a feature check, and it shipped in Chrome and Edge 137. Firefox doesn't parse it, and Safari has it only in a preview build. In my probe on Chromium 151, Firefox 153 and WebKit 26.5 it worked well, but it fails in one quiet way that the old CSS you keep as a fallback won't save you from.

Run this yourself. The probe behind every number below: css-if-function-support/ in the Coding Dunia code-examples repo. It loads one page in Chromium, Firefox and WebKit and prints 53 checks, and index.html in the same folder opens by hand.

Three conditional classes on a button is normal React. A size === 'lg' check, a size === 'sm' check, a clsx call, and a stylesheet with matching .btn--lg and .btn--sm rules. The CSS if() function folds that decision into one declaration, and those inline conditionals are why it keeps showing up in "no more JavaScript" threads.

Whether it deserves that reputation is a different matter. I ran it in three browser engines before writing a word of advice, and the result is more specific than "it's great" or "it's not ready".

What Does the CSS if() Function Actually Do?

if() returns the value attached to the first condition that is true. Each branch is a test, a colon, a value and a semicolon. The keyword else is a condition that is always true, so it goes last.

.btn {
  padding: 0.5rem 1rem;
  padding: if(
    style(--size: lg): 0.875rem 1.5rem;
    style(--size: sm): 0.25rem 0.625rem;
    else: 0.5rem 1rem
  );
}

There are three kinds of test. A style query is a comparison against a custom property on the element, as in style(--size: lg). media(width >= 600px) takes a media query. supports(color: lch(50% 0 0)) asks a feature question. Each accepts and, or and not, and MDN's page documents all three with examples. The spec lives in CSS Values and Units Level 5, and the W3C copy I opened calls itself an early exploration with major breaking changes expected. Keep that in mind before you build anything you can't rewrite in an afternoon.

One syntax detail bites people: there can't be a space between if and the parenthesis. I checked, and if ( was rejected even by Chromium and WebKit, which parse the function otherwise.

Where Does if() Replace Conditional Classes?

Conditional classes give way to if() when the component already hands CSS a custom property, and nothing else about the variant needs to change. In React that takes one line, because the style prop accepts custom properties:

import type { CSSProperties, ReactNode } from 'react';

type Size = 'sm' | 'md' | 'lg';

export function Button({ size = 'md', children }: { size?: Size; children: ReactNode }) {
  return (
    <button className="btn" style={{ '--size': size } as CSSProperties}>
      {children}
    </button>
  );
}

I should be upfront: I didn't run React for this article. The probe covers the CSS half, which is where the risk is, and the JSX side only sets a custom property. With the stylesheet above, the three buttons in the folder's index.html came out at 10px, 16px and 24px of left padding in Chromium 151 and WebKit 26.5, and at 16px for all three in Firefox 153, where the fallback line is all that survives.

So, is that better than className={clsx('btn', size === 'lg' && 'btn--lg')}? Honestly, no. A data attribute does the same job without any new syntax:

.btn[data-size='lg'] { padding: 0.875rem 1.5rem; }
.btn[data-size='sm'] { padding: 0.25rem 0.625rem; }

That works in every browser from the last decade and needs no fallback. I'd keep it as the default for plain variants and treat if() as a tool for the cases below, not as a way to retire clsx.

What Can if() Do That a Selector Can't?

Three things, and the probe verified each one: a media query inside an inline style, composition with calc() and nesting, and reading the element's own custom property, which a container style query can't do.

  • A media query inside an inline style. A style attribute can't hold an @media rule. It can hold width: if(media(width >= 600px): 300px; else: 100px), and on a fresh load I got 300px at an 800px viewport and 100px at 400px.
  • Composing with calc() and nesting. calc(if(style(--cols: 3): 3; else: 1) * 10px) gave 30px. A nested if() with a style() outer test and a media() inner test gave 51px wide and 52px narrow.
  • Reading its own element. A container style query can't match the element that sets the property, because container queries look at ancestors. if(style(--size: lg)) can, since it reads the element it sits on.

The third one has a measurement behind it. I set --tone: loud on an element and wrote @container style(--tone: loud) for that same element. Padding stayed at its base 1px. A child of an element that set the property matched and got 99px, in all three engines (caniuse lists container style queries from Firefox 151, so Firefox 153 agrees). if() sitting on the first element picked its branch at once in Chromium 151 and WebKit 26.5, with no ancestor needed.

CSS if() earns its place where a value has to follow a media query or a custom property and no selector can reach it, and everywhere else a data attribute is still the shorter and safer switch.

Share this Post on X Bluesky

Which Traps Did the Probe Find?

Four traps turned up, and every one of them fails quietly instead of throwing an error. One of them cost me the most time, and it isn't in the documentation I read, so it comes first.

A missing else throws away your fallback

The standard advice is to write the old value on the line before. It works if if() is unsupported, because the browser drops the whole declaration at parse time. It doesn't work if if() is supported and no branch matches, which is exactly what happens without an else.

.noelse {
  color: rgb(0, 0, 255);
  color: if(style(--missing: yes): rgb(255, 0, 0));
}
Three swatches: Chromium and WebKit render the parent's green, Firefox keeps the blue fallback line
The same two lines, three engines. Chromium 151 and WebKit 26.5 render the parent's green, Firefox 153 keeps blue.

Parent color was green. Chromium 151 and WebKit 26.5 computed rgb(0, 128, 0), the inherited value. Firefox 153 computed rgb(0, 0, 255). The supporting engines treat the declaration as invalid at computed-value time. Invalid at computed-value time means the declaration parsed fine but produced nothing usable, and it then behaves like unset, so the blue line never gets a say. An else branch, even one that repeats the default, closes the hole.

style() reads custom properties only

style(color: rgb(255, 0, 0)) parses cleanly. With a red color on the element it still took the else branch in Chromium 151 and WebKit 26.5, and MDN says container style queries don't support regular properties either. Nothing warns you. If a branch depends on a normal property, copy it into a custom property first.

Typos look like working branches

A misspelled custom property name in style() doesn't raise anything. The declaration parses, no branch matches, and you get the else value. Why does that matter? Because a branch that never fires looks exactly like a branch that fired and chose the default. Read the computed value in DevTools instead of trusting the stylesheet.

WebKit 26.5 didn't follow a resize

This one is specific to the build I ran, so read it with care. I loaded the page at 400px and widened the viewport to 800px. Chromium 151 moved the media() result from 100px to 300px. The WebKit 26.5 build bundled with Playwright stayed at 100px, while an ordinary @media (min-width: 600px) rule on the same page did update. It's the WebKit build Playwright bundles, not a Safari release, and the assertion in the example fails the day that changes. Still, it's a reason to test media() branches by resizing, not just by reloading.

How Do You Ship if() Today?

As an enhancement, behind a line every browser understands, and only after you've checked where support stands. Here is the state of the three engines on October 23, 2026, according to their own release notes and Can I use:

EngineStatusSource
Chrome and EdgeShipped in 137 (Chrome's release post is dated May 27, 2025)Chrome for Developers, Can I use
FirefoxNot supported through 161; Bugzilla 1981485 is open (filed August 2025, last changed October 11, 2026)Can I use, Bugzilla
SafariAdded to Technology Preview 249 on July 29, 2026; not in the Safari 27.0 release notes (September 14) or in Can I use through 27.1WebKit blog, Apple, Can I use

Can I use puts overall support at about 69 percent of users, and that's the number that decides it. A third of visitors getting the fallback is fine for a padding tweak and not fine for a layout that breaks.

The probe tested two guards. Both gave the right answer in all three engines, 20px where if() works and 7px where it doesn't:

.card {
  padding-left: 7px; /* every engine */
  padding-left: if(style(--size: lg): 20px; else: 10px);
}

.gate { padding-left: 7px; }
@supports (width: if(media(width > 0px): 1px; else: 2px)) {
  .gate { padding-left: if(style(--size: lg): 20px; else: 10px); }
}

The line-before pattern is shorter. The @supports gate is better when if() changes several declarations together and you want them all or none. In practice I'd go in this order:

  1. Write the old value first, on the line before.
  2. Give every if() an else that repeats the default.
  3. Put multi-declaration changes behind the @supports gate.
  4. Resize the window to check media() branches, don't just reload.
Table comparing Chromium 151, Firefox 153 and WebKit 26.5 on seven if() behaviors
What each engine did with if() in the probe: 53 checks, computed styles read back from real layout.

Pair this with a feature check the same way you would for anchor positioning, where an unsupported browser doesn't degrade gracefully, or for subgrid, which got an @supports guard of its own.

When Is if() the Wrong Layer?

When the decision isn't really about style. If size comes from props and drives behavior, markup or an ARIA state, JavaScript owns it, and the CSS should follow a data-* attribute that reflects the same fact. If the question is "how wide is the component's slot", you want container queries, not a viewport test in a value. And if five branches pile up in one declaration, you've written a lookup table inside a property, which reads worse than five small selectors.

I'm also wary of the spec status. A Working Draft that expects breaking changes, two engines without stable support, and a quirk in a third: that's a reason to use it for polish in a project you'll revisit, and a reason to keep it away from the one rule your checkout page needs.

What would change my mind? Firefox shipping it, and Safari's stable notes listing it. I'll rerun the probe when either happens, since the example is built to fail loudly.

Frequently Asked Questions

Which browsers support the CSS if() function in 2026?
Chrome and Edge, from version 137. Can I use shows no Firefox support through version 161 and no stable Safari support through 27.1. WebKit added if() to Safari Technology Preview 249 on July 29, 2026, but the Safari 27 release notes from September 14 don't list it. In my probe, Chromium 151 and the WebKit 26.5 build bundled with Playwright parsed it and Firefox 153 dropped the declaration. Treat it as progressive enhancement and write the old value on the line before it.
What happens when no if() condition is true and there is no else?
The function returns nothing, and the declaration becomes invalid at computed-value time, which behaves like unset. The trap is that your earlier fallback line doesn't help: the browser parsed the if() line as valid, so it replaced the old one. In my run on Chromium 151 a text color written as blue with an unmatched if() after it came out as the parent's green. Always finish with an else branch, even if it only repeats the default.
Can style() inside if() read regular CSS properties like color?
No, in the engines I tested it only matches custom properties. style(color: rgb(255, 0, 0)) parses without error, but with a red color on the element, Chromium 151 and WebKit 26.5 still took the else branch. MDN says the same about style queries: they support custom properties, not regular ones. If a branch depends on a normal property, expose the value as a custom property first and test that instead.
Is CSS if() better than conditional classes in React?
Not for plain variants. A data attribute selector such as .btn[data-size='lg'] is as short, works in every browser and needs no fallback. if() wins when a value has to change with a media query inside an inline style, or when one custom property has to drive several values in one place. Everywhere else, keep the selector, and keep if() for the cases a selector can't reach.