Plumeria 20.0
Plumeria v20.0 adds css.positionTry for anchor positioning fallbacks and accepts @starting-style as a key. It also stops accepting @layer and @scope, which is why this is a major release.
1. @layer and @scope are no longer accepted
In Plumeria, the style composed last wins: css.use(styles.base, styles.active) and classStyle={[styles.base, styles.active]} both mean active overrides base. Every at-rule key has to keep that promise, and these two cannot.
- A style inside
@layerloses to every unlayered style, wherever it is composed. - A style inside
@scopeis decided by how close its scope root is to the element, before composition order is looked at. This happens in Chromium, Firefox and WebKit alike.
So a key such as '@layer base' or '@scope (.card)' used to compile into a style whose place in the composition did not decide anything. Both are now rejected in css.create and as a css.createTheme selector: the compiler stops with an error, the types no longer accept them, and validate-at-rules reports them in the editor.
The keys that remain are @media, @container, @supports and @starting-style. A stylesheet that needs layers or scopes can still be written in global CSS next to Plumeria.
2. @starting-style
@starting-style sets the values an element transitions from when it first appears, such as a popover opening or a dialog being added to the page:
import * as css from '@plumeria/core';
const styles = css.create({
popover: {
opacity: 1,
transition:
'opacity 0.3s, display 0.3s allow-discrete, overlay 0.3s allow-discrete',
'@starting-style': {
opacity: 0,
},
},
});@starting-style {
.xgnu9yxr:not(#\#) {
opacity: 0;
}
}It composes like any other key: a style composed later that sets its own starting value wins. @starting-style takes no condition, so validate-at-rules reports one written after it.
3. css.positionTry
Anchor positioning places an element next to another one, and position-try-fallbacks lists where to go when it does not fit. A named fallback is an @position-try rule, which used to mean writing it in global CSS and repeating its name as a string.
css.positionTry returns the name instead:
import * as css from '@plumeria/core';
const above = css.positionTry({
positionArea: 'top',
marginBottom: 8,
});
const styles = css.create({
tooltip: {
position: 'absolute',
positionAnchor: '--trigger',
positionArea: 'bottom',
marginTop: 8,
positionTryFallbacks: `${above}, flip-inline`,
},
});@position-try --pt-xndu7ecb {
position-area: top;
margin-bottom: 8px;
}Like css.keyframes and css.varType, the name is a value you import, and the rule is emitted only when a style uses it. @position-try only accepts inset, margin, sizing and self-alignment properties, positionAnchor and positionArea, and the types allow only those.
4. Lint
no-unknown-css-propertiesreports a property that@position-trydoes not accept insidecss.positionTry.- The rules that read the styles in
css.keyframesandcss.viewTransitionreadcss.positionTryas well, andno-mutable-binding,no-destructureandno-inner-callcover it. sort-propertiesplaces the anchor positioning properties right afterposition.
5. Codemod
Exporting to CSS Modules wrote a lone condition such as classStyle={open && styles.panel} as className={open && styles.panel}, which does not type-check because className does not take false. It is now written as (open && styles.panel) || undefined, and adopting the project back reads it as classStyle={[open && styles.panel]}.
Upgrading
The @plumeria packages share one version number, so upgrade them together:
pnpm up "@plumeria/*" --latestRelease notes
20.0.0 (Oct 18, 2026)
- Feat:
css.positionTrydefines an@position-tryrule and returns its dashed name, and emits the rule only when the name is used in a value - Feat:
@starting-styleis accepted as a key incss.create, so an element can transition in from its starting values - Update:
@layerand@scopeare no longer accepted as keys incss.createor as acreateThemeselector, because a layered or scoped style is not decided by composition order;validate-at-rulesreports them - Feat:
no-unknown-css-propertiesreports properties that@position-trydoes not accept insidecss.positionTry - Feat: the rules that read styles in
css.keyframesandcss.viewTransitionalso readcss.positionTry, andno-mutable-binding,no-destructureandno-inner-callcover it - Update:
sort-propertiesplaces the anchor positioning properties right afterposition - Fix: the export codemod writes a lone
classStylecondition that can be false, such asopen && styles.x, as(open && styles.x) || undefined, sinceclassNamedoes not takefalse, and the adopt codemod reads it back intoclassStyle