Editor's Draft – 6 June 2025
Status: This document is an unofficial editor's draft. It has no formal standing within the W3C. It is published for discussion and early feedback.
CSS already provides conditional constructs such as @media, @supports, @container, and the in-progress @when / @else rules. These mechanisms succeed when the condition to test is of a single kind (viewport size, feature support, container size, etc.). Authors of design systems, however, frequently need an exclusive multi-branch switch whose branches can be defined by any Boolean combination of selectors, media queries, container queries, or previously named conditions.
This specification introduces two at-rules and a helper function that together fulfil that need:
@condition— defines a reusable Boolean condition alias.@switch/@case/@default— selects exactly one branch whose declarations participate in the cascade;@switch <ident>allows a named switch that can be wholly re-defined later in the same stylesheet.is()andhas()— selector-based functions inside Boolean expressions (is()matches the given selector list;has()matches when the element has the selector list in its relative scope).
Design goals:
- Expressiveness (cross-type Boolean logic, nesting, aliasing).
- Exclusivity (only one branch applies per
@switch). - Zero new cascade rules (the winning branch cascades exactly where the
@switchappears). - Easy compilation to today's CSS.
The key words MUST, MUST NOT, SHOULD, and MAY in this document are to be interpreted as described in [RFC 2119].
- Boolean Expression – an expression composed of
media()queries (whose arguments may use operators likewidth < 30rem),container()queries, the selector functionsis()/has(), and/or<ident>names defined by@condition, joined by the keywordsnot,and,or,xor, and parentheses for grouping. - Condition Alias – an
<ident>whose Boolean expression is provided by an earlier@conditionrule. - Winning Branch – the first
@caseinside an@switchwhose Boolean expression evaluates to true at computed-value time, or the@defaultbranch if present and no@casematches.
@condition <ident>: <boolean-expression> ;<ident>MUST NOT begin with the characters--(reserved for custom properties).- The rule MAY appear either in a top-level stylesheet context or inside a qualified rule (selector block). It MUST NOT appear inside an
@switchrule.
- The user agent stores the
<boolean-expression>under the specified<ident>in a Condition Map associated with the stylesheet. - If the same
<ident>is defined again in the same stylesheet, the last definition wins (shadowing earlier ones). - If evaluation of the stored Boolean expression would require referring to the
<ident>itself (directly or indirectly), the rule is invalid and ignored (cycle detection).
The grammar extends that used by CSS Conditional Rules Level 5.
<boolean-expr> = <boolean-term> ( 'or' <boolean-term> )*
<boolean-term> = <boolean-xor> ( 'xor' <boolean-xor> )*
<boolean-xor> = <boolean-factor> ( 'and' <boolean-factor> )*
<boolean-factor> = [ 'not' ] <boolean-atom>
<boolean-atom> = media( … )
| container( … )
| is( … )
| has( … )
| <ident>
| '(' <boolean-expr> ')'
is()andhas()use the same selector-argument grammar as their corresponding pseudo-classes. They are evaluated against the element at computed-value time.media()takes any as defined in Media Queries Level 4 and supports modern relational operators such aswidth < 30remorpointer = coarse.- Evaluation rules for
not,and,or,xorfollow standard Boolean logic, with operator precedence from highest to lowest:not>and>xor>or.
@switch [<ident>] {
@case <boolean-expression> { <declaration-list> }
[ @case <boolean-expression> { <declaration-list> } ]*
[ @default { <declaration-list> } ]?
}- The block MUST NOT contain declarations before the first
@case. - The block MUST contain at least one
@case. - At most one
@defaultis allowed and it MUST come after all@caserules. - If
<ident>is supplied, the switch becomes named; a later@switchwith the same<ident>in the same stylesheet replaces the earlier one in its entirety (all previous branches are discarded).
- For an unnamed
@switch, the UA builds an ordered list of branches consisting of all@caserules (in source order) followed by@defaultif present. - For a named
@switch, the UA stores this branch list under the given<ident>, replacing any previously-stored list with that name from the same stylesheet. - Each
@caseBoolean expression is parsed using the grammar in § 5, expanding any<ident>names via the Condition Map.
- At computed-value time, the UA evaluates each branch in list order until one is true.
- The declarations of the winning branch participate in the cascade in the same layer and position the
@switchrule occupies in the stylesheet. - Branches evaluated after the winner, as well as those not evaluated (because an earlier branch already won), contribute no declarations.
- If no
@caseevaluates to true and an@defaultexists, it becomes the winner; otherwise the@switchcontributes no declarations.
@condition mobile: media(width < 30rem);
@condition tablet: media(width >= 30rem) and media(width <= 60rem);
@condition touch: media(pointer = coarse);
@condition handheld: mobile or tablet;
@condition touch-handheld: handheld and touch;.card {
/* global defaults */
padding: 1rem;
background: var(--surface);
@switch {
@case mobile and is(.is-primary) {
background: var(--brand-dark);
color: white;
}
@case mobile {
background: var(--surface-alt);
}
@case tight {
border-radius: .25rem;
}
@default {
/* desktop & others */
transition: box-shadow .3s;
&:hover { box-shadow: 0 0 .5rem rgb(0 0 0 /.2); }
}
}
}@condition handheld: mobile or tablet;
@condition mobile: handheld; /* ← invalid: self-reference loop *//* Button component using native pseudo-class selectors */
.button {
padding-inline: var(--space-sm);
padding-block: var(--space-xs);
border: none;
border-radius: var(--radius-s);
color: var(--text-on-brand);
background: var(--brand-primary);
transition: background-color var(--transition-fast);
@switch theme {
@case is(:disabled) {
background: var(--surface-disabled);
color: var(--text-disabled);
cursor: not-allowed;
opacity: .6;
}
@case is(:active) {
background: var(--brand-primary-dark);
box-shadow: inset 0 1px 3px rgb(0 0 0 / .3);
transform: translateY(1px);
}
@case is(:hover) or is(:focus-visible) {
background: var(--brand-primary-light);
box-shadow: 0 0 0 2px var(--brand-outline);
}
@default {
/* resting state defined above */
}
}
}While not part of the formal language definition, the declarative nature of @condition, @switch, and selector-based Boolean functions enables a new class of tooling—human-written or AI-assisted—to make stylesheets smaller, clearer, and easier to maintain.
- Refactoring assistance – Static analyzers can detect recurring query patterns and suggest extracting them into shared
@conditionaliases, reducing duplication. - Intelligent auto-completion – Editors can surface valid alias names, flag unused ones, and warn against cycles in real time.
- Explain-why queries – Because each
@switchhas a single winning branch, tools can trace an element’s computed style back to the exact@casethat applies and present that explanation in natural language. - Legacy compilation – Build-time transformers can expand
@switchblocks to equivalent rules (@media,@supports, selectors) for browsers that lack native support, guided by project-specific compatibility targets. - Performance hints – Analytics-driven linters can recommend ordering
@cases so the most commonly true condition appears first, minimizing evaluation cost. - Test generation – By enumerating Boolean combinations that flip branch outcomes, automated test suites can achieve full visual-regression coverage with fewer handcrafted cases.
These capabilities are purely additive: they require no changes to the runtime behaviour specified in earlier sections, yet they materially enhance the author experience.
This feature is not expected to expose new fingerprinting surfaces beyond what exists today with dynamic class changes or attribute mutations. Nonetheless, implementers SHOULD review whether evaluation of arbitrary selectors inside is() or has() can leak user-specific data through timing attacks.
Authors may test UA support using the following @supports pattern:
@supports (condition-alias: true) {
/* UA understands @condition and @switch */
}The property name condition-alias is a feature gate token and has no computed value.
- 2025-06-06 – Initial editor's draft.
Early ideas, terminology, and Boolean-grammar inspiration come from the editors of CSS Conditional Rules Level 5, CSS Media Queries Level 4, and from community proposals by Tab Atkins Jr., Miriam Suzanne, Roman Komarov, and countless design-system authors.
- CSS Conditional Rules Module Level 5 (WD, 2024-11-05)
- CSS Media Queries Level 4 (REC)
- CSS Container Queries Level 3 (CR)
- Selectors Level 4 (WD)
- RFC 2119 – Key words for use in RFCs to Indicate Requirement Levels