CSS Gets a Conditional Function

The CSS Working Group recently resolved to add an if() function to the CSS Values Module Level 5 specification, following a proposal from Lea Verou. Verou demonstrated the potential of inline conditionals in a post on X:

background: if(style(–variant: success), var(–green));

The resolution isn't a finished feature — it's a green light for the CSSWG to develop a draft specification. Browser implementations are still a long way off, and the details are likely to shift during the process.

Why Inline Conditionals Matter

CSS has always been a conditional language — every selector is essentially a conditional, and the Cascade is the mechanism for evaluating those conditions. What's new with if() is the ability to write conditions inline, within a single declaration, rather than relying on separate rules with selectors.

The idea is not entirely novel. A similar proposal from 2018 didn't gain traction, in part because the syntax for conditions was largely undefined. This time, however, CSS already has a mature conditional mechanism in place: style queries. The conditions used by if() can reference the same syntax, plus media() and supports() functions drawn from Tab Atkins's @when proposal.

The Proposed Syntax

The basic structure looks like this:

<if()> = if( <container-query>, [<declaration-value>]{1, 2} )

where the first argument is a condition, the second is the value to use when the condition matches, and the optional third argument is the fallback value. If no third argument is provided, it's treated as an empty token stream. Values can be nested to create multiple branches.

When there's no fallback, the declaration resolves to an empty value rather than a default, which keeps the syntax readable:

.element {
  background-color:
    /* If the style declares the following custom property: */
    if(style(--variant: success),
      var(--color-green-50) /* Matched condition */
    );
}

Nesting can get deep. A more verbose form might have an if() inside an if() to handle multiple cases:

background-color: if(
  style(--variant: success), var(--color-success-60), 
    if(style(--variant: warning), var(--color-warning-60), 
      if(style(--variant: danger), var(--color-danger-60), 
        if(style(--variant: primary), var(--color-primary)
      )
    ),
  )
);

Lea Verou suggests a flatter alternative where conditions are declared outside the nested structure — each branch gets its own condition rather than building a nested tree:

<if()> = if( 
  [ <container-query>, [<declaration-value>]{2}  ]#{0, },
  <container-query>, [<declaration-value>]{1, 2} 
)

That same concept yields a flatter structure:

background-color: if(
  style(--variant: success), var(--color-success-60), 
  style(--variant: warning), var(--color-warning-60),
  style(--variant: danger), var(--color-danger-60), 
  style(--variant: primary), var(--color-primary)
);

Ties to Style Queries

The conditions in if() rely on the same style query mechanism used in container queries. Container queries implicitly assume size dimensions:

.element {
  background: var(--color-primary);

  /* Condition */
  @container parent (width >= 60ch) {
    /* Applied styles */
    background: var(--color-success-60);
  }
}

When the style() function is used instead, container queries become style queries:

.element {
  background: orangered;

  /* Condition */
  @container parent style(--variant: success) {
    /* Applied styles */
    background: dodgerblue;
  }
}

Viewed in isolation, style queries are an easy target for the question "What are they good for?" In the context of if(), though, they're clearly a building block for a broader conditional system.

Some challenges remain. Tab Atkins describes a possible ambiguity between the matched condition and the default value in certain cases. And while conditions like supports() and media() should be feasible, size queries present a problem:

I don't see why we couldn't do supports() and media(), but size queries would cause cycles with layout that are hard/impossible to even detect. (That's why we needed the restrictions we currently have for size CQs in the first place.)

Existing Workarounds

Developers have long crafted approaches to fake conditional custom properties:

  • Treating a custom property as a Boolean — applying styles based on whether it equals 0 or 1.
  • The "custom property toggle trick", where a placeholder property is set to a meaningful value based on the presence of another property.
  • Container Style Queries, but these can only style a container's descendants, not the container itself — so they can't be used to conditionally apply a value to the element being queried.

Verou's subsequent post on inline conditional statements in CSS today includes a detailed comparison table of these workarounds against the proposed if():

MethodInput valuesOutput valuesProsCons
Binary Linear InterpolationNumbersQuantitativeCan be used as part of a valueLimited output range
Togglesvar(--alias) (actual values are too weird to expose raw)AnyCan be used in part of a valueWeird values that need to be aliased
Paused animationsNumbersAnyNormal, decoupled declarationsTakes over animation property

Cascade weirdness
Type GrindingKeywordsAny value supported by the syntax descriptorHigh flexibility for exposed APIGood encapsulationMust insert CSS into light DOM

Tedious code (though can be automated with build tools)

No Firefox support (though that’s changing)
Variable animation nameKeywordsAnyNormal, decoupled declarationsImpractical outside of Shadow DOM due to name clashes

Takes over animation property

Cascade weirdness

References