What is if()?
The if() function lets you write conditional values directly inside a CSS property. Instead of duplicating selectors or nesting inside @media, you express the condition right where the value lives.
The Syntax
You can chain multiple conditions. The first one that matches wins. The else clause is optional — but if you omit it and nothing matches, the function returns a guaranteed-invalid value, which behaves like the property was never set (it falls back to its inherited or initial value).
/* Single condition with else */ color: if(style(--theme: dark): #fff; else: #000); /* Multiple conditions (first match wins) */ font-size: if( style(--size: sm): 0.75rem; style(--size: lg): 1.5rem; else: 1rem );
No Space Before the Parenthesis
Unlike most CSS functions, if() is invalid if there is a space between if and (. The entire declaration is thrown out silently.
/* ❌ INVALID — space before ( */ color: if (style(--theme: dark): white; else: black); /* ✅ VALID — no space */ color: if(style(--theme: dark): white; else: black);
What Can You Test?
if() supports three types of conditions, each wrapping a different kind of query.
style()
Test a CSS custom property value on the element itself.
media()
Test a media condition like viewport width or print mode.
supports()
Test whether the browser supports a feature before using it.
| Query type | Example | Use when… |
|---|---|---|
style() |
style(--variant: danger) |
Your value depends on a custom property |
media() |
media(max-width: 600px) |
Your value depends on the viewport or device |
supports() |
supports(display: grid) |
You need a feature-detection fallback |
Reacting to Custom Properties
The most powerful use of if(). Set a --custom-property on an element, then let every CSS value react to it inline.
Theme Toggle
One custom property, multiple properties all reacting inline:
.card { --theme: light; /* or "dark" — set this anywhere */ background: if(style(--theme: dark): #1a1a2e; else: #f0f4ff); color: if(style(--theme: dark): #e0e0ff; else: #2233aa); border: 2px solid if(style(--theme: dark): #4444aa; else: #aabbff); }
Size Variants
One --size property controls padding, font-size and border-radius simultaneously:
.btn { --size: md; padding: if(style(--size: sm): 6px 12px; style(--size: md): 12px 24px; else: 20px 40px); font-size: if(style(--size: sm): 0.75rem; style(--size: md): 1rem; else: 1.4rem); border-radius: if(style(--size: sm): 4px; style(--size: md): 8px; else: 14px); }
@container style() requires the element to be inside a named container ancestor — you query the parent. With if(style()), the element queries its own custom properties directly. No container-type wrapper needed.
Viewport-Aware Values
You can now write a media condition inside a property value instead of a separate @media block.
Before vs After
/* OLD WAY — two separate blocks */ .box { padding: 24px; } @media (max-width: 600px) { .box { padding: 12px; } } /* NEW WAY — inline with if() */ .box { padding: if(media(max-width: 600px): 12px; else: 24px); }
Feature Detection Inline
Use supports() to provide a safe fallback when a browser doesn't support something yet.
Progressive Enhancement
.box { /* Use oklch color if supported, otherwise plain hex */ color: if( supports(color: oklch(0.7 0.185 232)): oklch(0.7 0.185 232); else: #00adf3 ); /* Use grid if supported, fallback to flex */ display: if(supports(display: grid): grid; else: flex); }
supports() inside if() is most useful for future CSS features that land after if() itself — since a browser must already support if() to evaluate it. Using it to detect something like display: grid (universally supported) is mainly illustrative.
Going Further
Four powerful techniques you'll want in your toolkit.
if() Inside a Shorthand
if() can substitute a single token within a shorthand property — only that piece is conditional, the rest is fixed.
.card { --variant: default; /* Only the border-style part is conditional */ border: 2px blue if( style(--variant: dashed): dashed; style(--variant: dotted): dotted; else: solid ); }
if() substitutes a single value token within the shorthand. The parser resolves the whole shorthand after substitution — so it works as long as the surrounding tokens form a valid declaration.
Colon : vs Equals = in style()
There are two ways to compare in style(), and they behave very differently with computed values.
/* The ":" notation — string/exact match of computed values */ .box { --n: calc(6/2); background: if(style(--n: 3): red; else: green); /* → green! --n is "calc(6/2)" not "3" */ } /* The "=" notation — numeric comparison (evaluates the math) */ .box { --n: calc(6/2); background: if(style(--n = 3): red; else: green); /* → red! 6/2 == 3 ✓ */ }
: for string-like values (--status: error, --theme: dark). Use = for numeric comparisons when calc() is involved. The = notation evaluates the math first; : compares raw token strings.
Register with @property to Fix calc() Matching
When using the : notation, if your custom property holds a calc() expression, the browser treats it as a string — not a number. Registering it with @property forces evaluation.
/* ❌ Won't match — browser sees "calc(var(--n)/2)", not 3 */ .box { --f: calc(var(--n)/2); background: if(style(--f: 3): red; else: green); /* → green */ } /* ✅ Works — @property tells the browser --f is a <number> */ @property --f { syntax: "<number>"; inherits: false; initial-value: 0; } .box { --f: calc(var(--n)/2); background: if(style(--f: 3): red; else: green); /* → red ✓ */ }
Read HTML data-* Attributes with attr()
Bridge HTML state directly into CSS — no JavaScript class-toggling needed. Combine attr() with if() to style components based on their data-* attributes.
/* HTML */ <div class="card" data-status="pending">...</div> <div class="card" data-status="complete">...</div> /* CSS — read the attribute into a custom property, then test it */ .card { /* attr() with type(<custom-ident>) reads the data attribute */ --status: attr(data-status type(<custom-ident>)); border-color: if( style(--status: pending): royalblue; style(--status: complete): seagreen; else: gray ); background-color: if( style(--status: pending): #eff7fa; style(--status: complete): #f6fff6; else: #f7f7f7 ); }
Store if() Results in Custom Properties
You can assign an if() expression to a custom property. This keeps your declarations clean and avoids repeating the same conditions everywhere.
:root { --theme: "Emerald"; /* Compute once, reuse everywhere */ --bg: if( style(--theme: "Emerald"): hsl(146 50% 40%); style(--theme: "Amber"): hsl(43 74% 64%); else: hsl(0 0% 90%) ); --fg: if( style(--theme: "Emerald"): hsl(146 50% 3%); style(--theme: "Amber"): hsl(43 74% 3%); else: hsl(0 0% 10%) ); } /* Now just use the variables */ body { background: var(--bg); color: var(--fg); } header { border-bottom: 2px solid var(--bg); }
--theme variable at the root and let all your computed colours derive from it via if()-powered variables.
Check If a Custom Property Exists
You can test whether a custom property has any value (not just a specific value) by omitting the value in the condition. Useful for opt-in features.
/* Only show the icon element if --icon-family has been set */ .icon { display: if( style(--icon-family): inline-block; else: none ); } /* The component is hidden unless the caller sets --icon-family */ /* <span class="icon" style="--icon-family: 'Material Icons'"> → shown */ /* <span class="icon"> → hidden */
Quick Quiz
Seven questions. Pick the best answer for each.
if() statement?if(). Which one applies?supports() inside if() test?style() inside if() vs @container style() is…style(--n: 3) but --n is set to calc(6/2). Does the condition match?if() condition matches and there is no else?