CSS 'toggle()' Function: Conditional Styling Without @when
toggle() and @when get conflated constantly in blog posts despite solving completely unrelated problems. Neither ships in a browser yet, so here's what actually works today instead.
Paul Radford — Full-Stack Developer & Editor. Paul is a full-stack developer and editor of template.tips. Articles here are AI-drafted and reviewed by Paul, who knows the code well enough to catch what's wrong and cut the hype.

CSS's toggle() function is not a working substitute for @when. It is an old, still-unimplemented value function meant for cycling list marker and counter styles by nesting depth. If you need conditional styling in production today, reach for :has(), @supports, or container style queries, all of which ship in current browsers, unlike toggle() or @when.
That distinction matters because the two names get conflated in blog posts and social threads. @when is a genuinely new proposal for unifying conditional at-rules. toggle() is a much older idea with a completely different purpose that happens to share a tempting name with the JavaScript .toggle() method everyone already knows. Understanding what each one actually is, and what neither one currently does in a real browser, saves you from writing code that quietly does nothing.
What the Spec Actually Says About toggle()
toggle() is defined in the CSS Lists and Counters Module Level 3 draft. Its job is narrow: let a property value cycle through a list of options based on nesting depth, the same mechanism that already lets quotes alternate between open-quote and close-quote in nested blockquotes. The pitch was to generalize that alternation so you could, for example, rotate bullet styles in a deeply nested list without writing a :nth-child chain for every level.
1/* Hypothetical usage per CSS Lists and Counters Level 3.2 Nested list items would alternate marker style by depth.3 No shipping browser parses this function today, so this4 rule is simply ignored wherever it appears. */5ul.tree li: :marker {6 content: toggle(square, circle, disc);7}Nothing about this is "conditional styling" in the sense most developers mean when they say the phrase. It does not evaluate a media feature, a support flag, or an element's state. It walks a counter. That is a legitimate but small problem, and it explains why the function never generated much implementer interest.
Why Doesn't Any Browser Support toggle()?
Because nobody built it. The spec text for toggle() has carried an "at risk" label for years, which in CSSWG language means the feature can be dropped from the recommendation entirely without holding up the rest of the module. No rendering engine, Chromium, WebKit, or Gecko, has ever shipped an implementation, and there is no active bug tracking it toward release. caniuse does not even carry a table for it, because a feature needs at least one vendor to start work before there is anything to track.
This is worth internalizing as a general skill, not just a fact about one function: CSS specifications include plenty of at-risk and exploratory text that reads like settled syntax but has zero implementation behind it. If you copy a code sample from a spec document without checking implementation status first, you will ship dead weight. Always cross-reference a spec claim against MDN's browser compatibility tables or caniuse before you commit to a pattern in production CSS.
What Problem Was @when Supposed to Solve?
@when and its companion @else come from the CSS Conditional Rules Module Level 5 draft. The motivation is real and familiar to anyone who has nested @supports inside @media inside @container and lost track of the logic: @when proposes a single conditional at-rule that can test media features, support flags, and container queries with shared and, or, and not syntax, plus an @else branch for the negative case, similar to writing an if/else statement instead of three separate guard clauses.
As of early 2026, @when has not shipped in any stable release of Chromium, WebKit, or Gecko. It remains implementer-facing spec text, useful for understanding where conditional CSS is headed, but not something you can rely on in a shipping stylesheet. Treat it the way you would treat any editor's draft: a preview of intent, not a tool.
If Not toggle() or @when, How Do You Write Conditional Styles Today?
You already have the pieces. They are just spread across a few separate, well-supported features instead of one unified syntax.
Using :has() for State-Driven Conditionals
The relational pseudo-class :has() lets a parent's style depend on what is inside it, which covers a huge share of what people reach for @when to imagine solving.
1/* Style a form field's wrapper based on the validity of the2 input inside it, no JavaScript and no future syntax needed. */3.field: has(input:invalid) {4 border-color: #c0392b;5 background-color: #fdecea;6}78.field: has(input:valid:not(:placeholder-shown)) {9 border-color: #2e7d32;10}Support here is solid: Safari shipped :has() in Safari 15.4 (March 2022), Chrome followed in Chrome 105 (September 2022), and Firefox caught up in Firefox 121 (December 2023). By 2026 you can use it without a fallback for the vast majority of audiences. Full details and current compatibility figures are on MDN's :has() page and the caniuse table.
Using Container Style Queries for Contextual Conditionals
For layout decisions that depend on context rather than element state, container style queries do the job that @when gets credit for in a lot of demos.
1/* Container style query: adjust a card's internal layout2 based on a custom property set higher up the tree. This3 is a real, shippable stand-in for conditional styling. */4@container style(--layout: compact) {5 .card {6 grid-template-columns: 1fr;7 gap: 0.5rem;8 }9}Style queries arrived later than size-based container queries and rolled out unevenly across engines through 2023 and 2024. Test them directly in your target browsers rather than assuming parity, because this is one of the areas where "supports container queries" and "supports style container queries" are not the same claim.
Where toggle() Would Still Be Useful, If Implemented
It is worth being fair to the original idea. Cycling marker styles by nesting depth is a real, if small, pain point. Today the workaround is verbose: chain :nth-child() selectors per level, or reset a CSS counter and use counter() inside content with manual modulo logic. toggle() would have made that one line. It just never got far enough to matter, and there is no indication in current CSSWG working drafts that implementation is imminent, so plan around the workaround, not the shortcut.
When You Should Just Wait, and When You Shouldn't
The decision here is not really about toggle() specifically, it is about how you treat any speculative CSS feature. If a function or at-rule has zero shipping implementations and no browser bug in active development, it belongs in your reading list, not your build. If a feature has landed in at least two engines and has a caniuse entry showing real coverage, like :has() does now, it belongs in your production stylesheet with a plain @supports fallback for the remaining audience.
1/* Feature-detect selector support before relying on it,2 the same discipline you'd apply to any newer CSS feature. */3@supports selector(: has(a)) {4 .nav-item: has(> a: focus-visible) {5 outline: 2px solid dodgerblue;6 }7}89@supports not selector(: has(a)) {10 .nav-item > a: focus-visible {11 outline: 2px solid dodgerblue;12 }13}Testing Your Conditional CSS Before You Ship It
Do not trust a code sample from a spec draft, a conference talk, or a blog post, mine included, without checking it against a real browser. Open the current stable release of Chrome, Firefox, and Safari and paste your candidate rule into DevTools. If the property or function shows up struck through or gets flagged as an unsupported value in the Styles panel, it is not implemented, no matter how confidently it is written in the specification. Cross-check against MDN's compatibility tables, since they track implementation status per engine and per version, which a spec document never will. For anything genuinely load-bearing, like a validation state or a layout branch, build the :has() or @supports version first, and revisit @when only once it shows up in an actual browser release note, not before.
Related Articles

How to use the advanced CSS attr() function for types and units
attr(data-color color, #333) feeding straight into background-color sounds like it already ships, but as of early 2026 it's spec-only. Here's what attr() actually does today, and what's coming.

Niche CSS Pseudo-Elements You Should Know: ::before Through ::highlight
Most developers use two or three CSS pseudo-elements regularly and forget the rest exist entirely. Here's what ::marker, ::backdrop, and ::highlight actually do, and where each one breaks.

Animating to height auto using CSS interpolate-size
height: auto was never a number the browser could interpolate toward, which is exactly why it never animated at all. These two features finally give the engine something concrete to target.