Plumeria 18.1
2026-07-30
Plumeria v18.1 removes a compiler restriction: a bracket access with a runtime key can now sit inside a condition. Lifting it turned out to be the smaller half of the change — the way conditions are compiled was reworked underneath, and existing code gets smaller output for free.
Three things land together:
enabled ? styles[variant] : styles.disabledcompiles — the error is gone.- Nested conditions stay linear — a three-branch ternary used to emit seven entries; it now emits three.
- A compiler fix —
@plumeria/compileremitted no rules at all for a bracket group under a condition.
Nothing about your code has to change.
1. The restriction
Until 18.1, this failed the build:
<button classStyle={enabled ? styles[variant] : styles.disabled} />Plumeria: "styles[variant]" cannot be used inside a condition, because its
bracket key is not a literal.
Move the condition into the brackets instead, e.g. styles[cond ? 'a' : 'b'].The suggested rewrite works, and it is still the tidier way to say it when it fits. But it does not always fit — the two branches often come from different objects, or one branch is not a bracket access at all — and there was no reason for the compiler to insist.
It now compiles:
({"primary":"xd2z9dcc","danger":"x1hugtvn","ghost":"x6tgf1st","disabled":"xwepo8yj","#0":"xwepo8yj"}
[((enabled) ? variant : "#0")] || "")One lookup. The condition folded into the key expression, and #0 is the slot the alternate branch took. && works the same way, with the false case landing on a key no branch claims:
<button classStyle={enabled && styles[variant]} />({"primary":"xd2z9dcc","danger":"x1hugtvn","ghost":"x6tgf1st","disabled":"xwepo8yj"}
[((enabled) ? variant : "")] || "")2. Why it was there, and why it is not
Plumeria has no runtime resolver. Every class string a component can produce is computed at build time, so the compiler has to enumerate the possibilities — which means the syntax it accepts has to keep that number bounded. That is a real constraint, and it is the reason the restriction looked reasonable.
It was the wrong reading of this case. The branches of a condition are mutually exclusive. Only one of them ever applies, so putting a group in one of them adds to the number of outcomes; it does not multiply it. A ternary over two groups of N and M keys has N + M outcomes, not N × M.
Multiplication comes from a different place — sources that are all active at once and merge into the same property:
// Two conditions, both live: four combinations to precompute
<div classStyle={[a && styles.primary, b && styles.danger]} />That has always been the case and still is. The old restriction was guarding the wrong operation.
What changed underneath
An argument is now compiled as a single decision tree rather than one dimension per branch. The tree becomes the key expression, and each of its leaves gets one entry:
<button classStyle={a ? (b ? styles.primary : styles.danger) : styles.ghost} />({"#0":"xd2z9dcc","#1":"x1hugtvn","#2":"x6tgf1st"}
[((a) ? ((b) ? "#0" : "#1") : "#2")] || "")Three branches, three entries. A leaf that is a bracket group contributes its own keys instead of a generated one, which is what makes the two features the same change.
3. Nested conditions got smaller
This is where existing code benefits without touching anything. Previously each branch became its own dimension and the compiler enumerated every combination of them — including the ones that can never occur together:
| Expression | Outcomes | Before | After |
|---|---|---|---|
a ? (b ? s.p2 : s.p1) : s.p3 | 3 | 7 | 3 |
a ? (b ? s.p2 : (c ? s.p1 : s.p4)) : s.p3 | 4 | 15 | 4 |
a ? (b ? s[k] : s.p1) : s.p3 | N + 2 | 19 | 6 |
The old cost doubled with every level of nesting. It is now exactly the number of branches you wrote.
The class names your components render are unchanged — byte for byte, for every input. Only the table the compiler ships got smaller. Simple forms that were already optimal are untouched: a ? styles.x : styles.y still compiles to a plain ternary between two class strings, not a lookup.
4. @plumeria/compiler emitted nothing
Separately from the above, the standalone compiler had a hole. Where the bundler plugins raised an error, compileCSS accepted the same code and silently produced no rules for it:
<button classStyle={enabled && styles[variant]} />Before: no CSS at all for any key of styles. The class names were emitted into the markup with nothing behind them.
The cause was that its conditional walk reported itself handled as soon as it entered a branch, so the bracket group inside never reached the code that collects styles. Every key of a group is reachable at runtime — being nested under a condition narrows nothing — so all of them are emitted now.
If you use @plumeria/compiler directly, this is the fix worth upgrading for.
Migration
None for 18.1.0. It only accepts more than it did before, and the output for code that already compiled is identical.
If you moved a condition into the brackets to work around the old error, you can leave it exactly as it is — styles[cond ? 'a' : 'b'] remains one lookup and is still the clearest form when both branches come from the same object.
18.1.1 does change output
A property set by more than one source used to be resolved by a fixed precedence — a plain style lost to a condition, and a condition lost to a bracket group — no matter which order they were written in. It now follows the order you wrote, the rule two plain styles have always followed:
// The condition comes last, so it wins. Until 18.1.1 the group did.
<div classStyle={[styles.base, sizeStyles[size], isMuted && styles.muted]} />Only a list that mixes kinds and has two of them set the same property is affected. Where the order you wrote already agreed with the old precedence nothing changes; where it did not, the style written last now applies — usually the one that was being silently dropped.
Release notes
18.1.8 (Aug 5, 2026)
- Update dependencies (postcss 8.5.25, GHSA-r28c-9q8g-f849)
Thank you for reporting this as an issue.
18.1.7 (Aug 5, 2026)
- Update: README.md
18.1.6 (Aug 4, 2026)
- Update README.md
- Update: nextjs
- Fix: name the css variable of a dynamic function key after the declaration it resolves to, so two keys that share a parameter name no longer overwrite each other on one element
- Fix: set the variable for a parameter that only appears under a nested selector or an at-rule
- Fix: resolve a dynamic function key written inside a condition, instead of dropping the branch it belongs to
- Fix: read the named parameters of a destructured signature, and fold such an argument into the rule only when its value is written out in full
- Fix: report a dynamic function key that cannot reach the element, instead of emitting a call that throws at runtime
- Note: the class name and the css variable of a dynamic function key are derived from the declaration they resolve to, so both names change in this release
- Note: a dynamic function key that could not reach the element used to compile into a broken call or a dropped style, and now fails the build with the reason
18.1.4 (Aug 2, 2026)
- Fix: keep the virtual css id relative so a bundler cannot print the author's directories into the stylesheet it ships
- Fix: strip the annotation naming the virtual css from the emitted stylesheet on esbuild and Bun
18.1.2 (Aug 2, 2026)
- Fix: tell two bracket groups apart when one condition folds both into a lookup, a key both objects carry no longer answers with the later group's style
- Fix: number a bracket dimension's keys in a compound lookup, a key holding the
__the compound key is joined with no longer blurs where one dimension ends
18.1.1 (Jul 31, 2026)
- Fix: merge the sources of a styling prop in the order they are written, an unconditional style placed after a condition no longer loses to it
- Fix: a condition placed after a bracket group is no longer overridden by that group
18.1.0 (Jul 30, 2026)
- Feat: accept a non-literal bracket key inside a condition,
enabled ? styles[variant] : styles.disabledno longer fails to compile - Feat: fold an argument's mutually exclusive branches into one lookup, so nesting no longer doubles the generated table per level
- Fix: emit CSS for a bracket group reached through a condition,
enabled && styles[variant]produced no rules at all
Thanks for building with Plumeria! If you have any feedback or questions, please let us know on GitHub Discussions or GitHub Issues.