CSS Conditional Rules Module Level 5

W3C Working Draft,

More details about this document
This version:
https://www.w3.org/TR/2026/WD-css-conditional-5-20260630/
Latest published version:
https://www.w3.org/TR/css-conditional-5/
Editor's Draft:
https://drafts.csswg.org/css-conditional-5/
History:
https://www.w3.org/standards/history/css-conditional-5/
Feedback:
CSSWG Issues Repository
Inline In Spec
Editors:
L. David Baron (Google)
Elika J. Etemad / fantasai (Apple)
Chris Lilley (W3C)
Miriam E. Suzanne (Invited Expert)
Lea Verou (Invited Expert)
Suggest an Edit for this Spec:
GitHub Editor
Delta Spec:
yes
Test Suite:
https://wpt.fyi/results/css/css-conditional/

Abstract

This module contains the features of CSS for conditional processing of parts of style sheets, based on capabilities of the processor or the environment the style sheet is being applied in. It includes and extends the functionality of CSS Conditional 4 [css-conditional-4], adding the generalized conditional rule @when and the chained conditional rule @else, as well as introducing font processing queries to the supports query syntax used in @supports rules, and container queries.

CSS is a language for describing the rendering of structured documents (such as HTML and XML) on screen, on paper, etc.

Status of this document

This section describes the status of this document at the time of its publication. A list of current W3C publications and the latest revision of this technical report can be found in the W3C standards and drafts index.

This document was published by the CSS Working Group as a Working Draft using the Recommendation track. Publication as a Working Draft does not imply endorsement by W3C and its Members.

This is a draft document and may be updated, replaced or obsoleted by other documents at any time. It is inappropriate to cite this document as other than a work in progress.

Please send feedback by filing issues in GitHub (preferred), including the spec code “css-conditional” in the title, like this: “[css-conditional] …summary of comment…”. All issues and comments are archived. Alternately, feedback can be sent to the (archived) public mailing list www-style@w3.org.

This document is governed by the 18 August 2025 W3C Process Document.

This document was produced by a group operating under the W3C Patent Policy. W3C maintains a public list of any patent disclosures made in connection with the deliverables of the group; that page also includes instructions for disclosing a patent. An individual who has actual knowledge of a patent that the individual believes contains Essential Claim(s) must disclose the information in accordance with section 6 of the W3C Patent Policy.

1. Introduction

This is currently an early draft of the things that are new in level 5. The features in Level 3 and Level 4 are still defined in [css-conditional-3] and [css-conditional-4] and have not yet been copied here.

CSS Conditional Level 5 extends the @supports rule and supports query syntax to allow testing for custom support conditions as well as supported at-rules and font technologies.

It also adds an @when rule, which generalizes the concept of a conditional rule. Anything that can be expressed in an existing conditional rule can be expressed in @when by wrapping it in an appropriate function to declare what kind of condition it is. This allow authors to easily combine multiple types of queries, such as media queries and supports queries, in a single boolean expression. Without this, authors must rely on nesting separate conditional rules, which is harder to read and write, presupposes the conditions are to be conjoined with the “and” boolean relation (with no easy way to indicate anything else), and restricts their utility in the proposed conditional rule chains.

It also adds @else rules, which immediately follow other conditional rules and automatically qualify their conditions as the inverse of the immediately preceding rule’s conditions, such that only the first matching rule in a conditional rule chain is applied.

It also adds Container Queries. They are conceptually similar to Media Queries, but allow testing aspects of elements within the document (such as box dimensions or computed styles), rather than on the document as a whole.

2. Extensions to the @supports rule

This level of the specification extends the <supports-feature> syntax as follows:

<supports-feature> = <supports-selector-fn>
                   | <supports-font-tech-fn> | <supports-font-format-fn>
                   | <supports-at-rule-fn> | <supports-named-feature-fn>
                   | <supports-env-fn> | <supports-decl>
<supports-decl> = ( [ <declaration> | <supports-condition-name> ] )
<supports-font-tech-fn> = font-tech( <font-tech> )
<supports-font-format-fn> = font-format( <font-format> )
<supports-at-rule-fn> = at-rule( <at-keyword-token> )
<supports-named-feature-fn> = named-feature( <ident> )
<supports-env-fn> = env( <ident> )

<declaration> here matches anything that would be successfully parsed by consume a declaration, ignoring the context-validation check at the end of that algorithm. Notably, this includes a trailing !important, which is valid but ignored for the purpose of @supports.

<supports-condition-name>

The result is true if the UA supports the named condition. If the name is not recognized, the result is false.

font-tech( <font-tech> )

The result is true if the UA supports the font tech provided as an argument to the function.

font-format( <font-format> )

The result is true if the UA supports the font format provided as an argument to the function.

at-rule( <at-keyword-token> )

The result is true if the UA supports the at-rule provided as an argument to the function.

named-feature( <ident> )

The result is true if the UA supports the named feature provided as an argument to the function.

env( <ident> )

The result is true if the UA supports the environment variable provided as an argument to the function.

2.1. Extensions to the definition of support

2.1.1. Font techs and formats

Tests

A CSS processor is considered to support a font tech when it is capable of utilizing the specified CSS Fonts 4 § 11.1 Font tech in layout and rendering.

A CSS processor is considered to support a font format when it is capable of utilizing the specified CSS Fonts 4 § 11.2 Font formats in layout and rendering, and this format is not specified as a <string>.

2.1.2. At-rules

Tests

A CSS processor is considered to support an at-rule if it would accept an at-rule beginning with the specified at-keyword within any context.

Note: Because @charset is not a valid at-rule, it is not considered to be supported under this definition.

2.1.3. Named features

A CSS processor is considered to support a named feature if it supports the named feature based on the feature definition described in the following list:

anchor-position-follows-transforms

Anchoring to a transformed element automatically takes into account the anchor’s transforms, causing the positioned element to shift to match it. [CSS-ANCHOR-POSITIONING]

(An earlier version of the specification did not take transforms into account.)

single-axis-scroll-container

The ability to have single-axis scroll containers, where one axis is scrollable and the other is clip. [CSS-OVERFLOW-4]

(Previously, specifying clip alongside a scrollable value caused it to compute to hidden, which is still a scrollable value even if it doesn’t generate a scrollbar.)

If the feature is not listed the processor does not support the named feature.

Note: The CSS Working Group intends to add features to this list rarely, when there is real demand for feature testing something specific and it would be unreasonable to add a more general feature testing mechanism that would cover the use case.

2.1.4. Named conditions

A CSS processor is considered to support a named condition when the related named supports condition returns true.

2.1.5. Environment variables

A CSS processor is considered to support an environment variable if the <ident> is a supported environment variable.

3. Generalized Conditional Rules: the @when rule

The @when at-rule is a conditional group rule that generalizes the individual conditional group rules such as @media and @supports. It is defined as:

@when <boolean-condition> {
  <rule-list>
}

Where <boolean-condition> is a boolean algebra a la Media Queries 4 § 3 Syntax, but with media() and supports() functions as leaves.

Define "boolean algebra, with X as leaves" in a generic way in Conditional, so all the conditional rules can reference it directly, rather than having to redefine boolean algebra on their own.

The media() and supports() functions are defined as:

media() = media( [ <mf-plain> | <mf-boolean> | <mf-range> ] )
supports() = supports( <declaration> )

A media() or supports() function is associated with the boolean result that its contained condition is associated with.

4. Chained Conditionals: the @else rule

Usually, conditional group rules are independent; each one has a separate condition evaluated without direct reference to any other rule, and decides whether or not to apply its contained rules based solely on its condition.

This is fine for simple conditions, but makes it difficult to write a collection of conditionals that are meant to be mutually exclusive: authors have to very carefully craft their conditions to not activate when the other rules are meant to, and make sure the collection of conditionals don’t accidentally all exclude some situation which is then left unstyled.

The @else rule is a conditional group rule used to form conditional rule chains, which associate multiple conditional group rules and guarantee that only the first one that matches will evaluate its condition as true. It is defined as:

@else <boolean-condition>? {
  <rule-list>
}

@else is interpreted identically to @when. If its <boolean-condition> is omitted, it’s treated as having a condition that’s always true.

A conditional rule chain is a series of consecutive conditional group rules, starting with a conditional group rule other than @else, followed by zero or more @else rules. There cannot be anything between the successive conditional group rules other than whitespace and/or comments; any other token “breaks” the chain.

Should we require that only the last @else in a chain can have an omitted condition? It’s not uncommon for me, when debugging code, to short-circuit an if-else chain by setting one of them to "true"; I presume that would be similarly useful in CSS? It’s still pretty easy to see you’ve done something wrong if you omit the condition accidentally.

Within a conditional rule chain, the conditions of each conditional group rule are evaluated in order. If one of them is true, the conditions of all following conditional group rules in the chain evaluate to false, regardless of their stated condition.

An @else rule that is not part of a conditional rule chain is invalid and must be ignored.

For example, here’s a (somewhat silly) conditional chain:
@when media(width >= 400px) and media(pointer: fine) and supports(display: flex) {
  /* A */
} @else supports(caret-color: pink) and supports(background: double-rainbow()) {
  /* B */
} @else {
  /* C */
}

Exactly one of the preceding rules will be chosen, even though the second rule doesn’t exclude large widths, fine points, or flexbox support, and the last rule doesn’t specify anything at all.

To achieve the same result without conditional rule chains, you’d need to write:

@media (width >= 400px) and (pointer: fine) {
  @supports (display: flex) {
  /* A */
  }
  @supports not (display: flex) {
  @supports (caret-color: pink) and (background: double-rainbow()) {
    /* B */
  }
  @supports not ((caret-color: pink) and (background: double-rainbow())) {
    /* C */
  }
  }
}
@media not ((width >= 400px) and (pointer: fine)) {
  @supports (caret-color: pink) and (background: double-rainbow()) {
  /* B */
  }
  @supports not ((caret-color: pink) and (background: double-rainbow())) {
  /* C */
  }
}

This is simultaneously hard to read, requires significant duplication of both conditions and contents, and is very difficult to write correctly. If the conditions got any more complicated (which is not unusual in real-world content), the example would get significantly worse.

In this example, three different color font technologies are tested, in order of preference, plus a monochrome fallback. The most capable, COLRv1, supports both gradients and font variations; the next best choice, SVG, supports gradients while the least capable, COLRv0, supports flat color fill only.

The fallback has no test condition, so will always be chosen unless one of the earlier conditions succeeds.

@when font-tech(color-COLRv1) and font-tech(variations) {
  @font-face { font-family: icons; src: url(icons-gradient-var.woff2); }
}
@else font-tech(color-SVG) {
  @font-face { font-family: icons; src: url(icons-gradient.woff2); }
}
@else font-tech(color-COLRv0) {
  @font-face { font-family: icons; src: url(icons-flat.woff2); }
}
@else {
  @font-face { font-family: icons; src: url(icons-fallback.woff2); }
}

Notice that in this example, the variable color font is only downloaded if COLRv1 is supported and font variations are also supported.

Notice too that only one of the available options will be downloaded; this would not be the case without @when and @else, as the next example shows.

In this example, although it appears that the fallback will not be used if COLRv1 is supported, in fact both fonts will be downloaded, which wastes bandwidth if it is not used.

The fallback might still be used for some characters; for example, if the color font supports only Latin, while the fallback supports Latin and Greek.

@font-face { font-family: icons; src: url(icons-fallback.woff2);
@supports font-tech(color-COLRv1) {
  @font-face { font-family: icons; src: url(icons-gradient-var.woff2); }
}

5. Container Queries

While media queries provide a method to query aspects of the user agent or device environment that a document is being displayed in (such as viewport dimensions or user preferences), container queries allow testing aspects of elements within the document (such as box dimensions or computed styles).

By default, all elements are query containers for the purpose of container style queries, and can be established as query containers for container size queries and container scroll-state queries by specifying the additional query types using the container-type property (or the container shorthand). Style rules applying to a query container’s flat tree descendants can be conditioned by querying against it, using the @container conditional group rule.

An ancestor element that generates a box is not an eligible container for container size queries if that box is not an ancestor box of any boxes generated by the querying element.

There are cases where the box for a pseudo-element is generated as a sibling box of its originating element’s box. If we allowed querying the size of a sibling box, it would introduce layout cycles.

For example, the ::scroll-marker-group and ::scroll-button() pseudo-elements generate sibling boxes of their originating element’s box. These pseudo-elements will not be able to query their originating scroller for container size queries. They will, however, be able to query other eligible query container ancestors.

For example, we can define the main content area and sidebar as containers, and then describe a .media-object that changes from vertical to horizontal layout depending on the size of its container:
main, aside {
  container: my-layout / inline-size;
}

.media-object {
  display: grid;
  grid-template: 'img' auto 'content' auto / 100%;
}

@container my-layout (inline-size > 45em) {
  .media-object {
  grid-template: 'img content' auto / auto 1fr;
  }
}

Media objects in the main and sidebar areas will each respond to their own container context.

For the ::part() and ::slotted() pseudo-element selectors, which represent real elements in the DOM tree, query containers can be established by flat tree ancestors of those elements. For other pseudo-elements, query containers can be established by inclusive flat tree ancestors of their originating element.

It follows that:
A ::before selector querying the size of the originating element:
<style>
  #container {
  width: 100px;
  container-type: inline-size;
  }
  @container (inline-size < 150px) {
  #inner::before {
    content: "BEFORE";
  }
  }
</style>
<div id=container>
  <span id=inner></span>
</div>
A ::slotted() selector for styling a shadow host child can query a container in the shadow tree:
<div id=host style="width:200px">
  <template shadowroot=open>
  <style>
    #container {
    width: 100px;
    container-type: inline-size;
    }
    @container (inline-size < 150px) {
    ::slotted(span) {
      color: green;
    }
    }
  </style>
  <div id=container>
    <slot />
  </div>
  </template>
  <span id=slotted>Green</span>
</div>

5.1. Creating Query Containers: the container-type property

Name: container-type
Value: normal | [ [ size | inline-size ] || scroll-state ]
Initial: normal
Applies to: all elements
Inherited: no
Percentages: n/a
Computed value: specified keyword
Canonical order: per grammar
Animation type: not animatable

The container-type property establishes the element as a query container for certain types of queries. For size container queries, which require certain types of containment, elements are explicitly made query containers through this property. For other types of query containers any element can be a query container, such as for container style queries.

Values have the following meanings:

size
Establishes a query container for container size queries on both the inline and block axis. Applies style containment and size containment to the principal box, and establishes an independent formatting context.
inline-size
Establishes a query container for container size queries on the container’s own inline axis. Applies style containment and inline-size containment to the principal box, and establishes an independent formatting context.
scroll-state
Establishes a query container for container scroll-state queries. For queries that match the state of the scroller itself (such as scrollable), the element is only a query container on the element’s own scrollable axises.

Note: This means that a scrollable query, for example, could query against different containers for scroll-state(scrollable: block) and scroll-state(scrollable: inline) queries.

normal
The element is not a query container for any container size queries or container scroll-state queries, but remains a query container for container style queries.
For example, authors can create container-responsive typography, adjusting font-size, line-height, and other typographic concerns based on the size of a container:
aside, main {
  container-type: inline-size;
}

h2 { font-size: 1.2em; }

@container (width > 40em) {
  h2 { font-size: 1.5em; }
}

The 40em value used in the query condition is relative to the computed value of font-size on the relevant query container.

Containers can also expose computed style values for querying. This can be useful for toggling behavior across multiple properties:
@container style(--cards: small) {
  article {
  border: thin solid silver;
  border-radius: 0.5em;
  padding: 1em;
  }
}
Containers can also expose state that depends on scroll offset. This example styles a descendant of a sticky positioned element when it is stuck to the top edge:
#sticky {
  container-type: scroll-state;
  position: sticky;
}
@container scroll-state(stuck: top) {
  #sticky-child {
  background-color: lime;
  }
}

5.2. Naming Query Containers: the container-name property

Name: container-name
Value: none | <custom-ident>+
Initial: none
Applies to: all elements
Inherited: no
Percentages: n/a
Computed value: the keyword none, or an ordered list of identifiers
Canonical order: per grammar
Animation type: not animatable
Tests

The container-name property specifies a list of query container names. These names can be used by @container rules to filter which query containers are targeted. Container names are not tree-scoped names.

none
The query container has no query container name.
<custom-ident>
Specifies a query container name as an identifier. The keywords none, and, not, and or are excluded from this <custom-ident>.
In some cases, we want to query aspects of a specific container, even if it’s not the nearest ancestor container. For example, we might want to query the height of a main content area, and the width of a more nested inline-container.
main {
  container-type: size;
  container-name: my-page-layout;
}

.my-component {
  container-type: inline-size;
  container-name: my-component-library;
}

@container my-page-layout (block-size > 12em) {
  .card { margin-block: 2em; }
}

@container my-component-library (inline-size > 30em) {
  .card { margin-inline: 2em; }
}

It is also possible to query for a container only based on its name.

@container my-page-layout {
  .card { padding: 1em; }
}

5.3. Creating Named Containers: the container shorthand

Name: container
Value: <'container-name'> [ / <'container-type'> ]?
Initial: see individual properties
Applies to: see individual properties
Inherited: see individual properties
Percentages: see individual properties
Computed value: see individual properties
Animation type: see individual properties
Canonical order: per grammar
Tests