astryx-svelte v0.3.1: the setup step is gone

Install, import two stylesheets, done. Plus the RTL bug a new stylesheet oracle found on its first run, and the cascade defect hiding in four documented recipes.
Rohit Kushwaha
Aug 11, 2026
5 min read
0.3.0 shipped with a setup step nobody should have had to perform. Installing the package was not enough: your bundler also had to run the StyleX compiler, and it had to run it with the same options this package's own build uses. Get that wrong and nothing failed. No error, no warning — the components rendered, with correct markup and no styling at all.
0.3.1 deletes the step.
ts
import { Button } from '@astryx-svelte/core';
import '@astryx-svelte/core/base.css';
import '@astryx-svelte/core/astryx.css';
That is the whole setup now.

The fix was a missing port, not a new feature

The 0.3.0 release notes explained the setup step as intrinsic: "Astryx's own package ships pre-built CSS; this package cannot." That sentence was wrong, and it was wrong in the most ordinary way — by never checking.
Upstream publishes @astryxdesign/core/astryx.css, generated by a post-build script in their repo. We had simply not ported that script. So the "limitation" was an omission wearing a justification, and the fix was to do what upstream does: compile the styles at publish time and ship the stylesheet.
Porting it settled a question that had been open for months. useCSSLayers had been on the backlog as "align with upstream's form", and reading their generator answered it: they call processStylexRules(rules, false) and wrap the result in a single @layer astryx-base. Priority is expressed as :not(#\#) specificity padding, not as @layer priority1…9. So a consumer ordering layers around Astryx orders around one layer name — and it has to be upstream's name, or CSS written for Astryx does not transfer.

Shipping the stylesheet was not enough

The first version of this change published astryx.css and stopped there. It did not work, and the way it failed is worth recording.
dist shipped uncompiled — svelte-package transpiles TypeScript and does not run StyleX — so the published modules still contained literal stylex.create calls. The assumption was that those would degrade to a runtime no-op and the stylesheet would supply the classes.
They do not degrade. They throw:
Unexpected 'stylex.create' call at runtime. Styles must be compiled by '@stylexjs/babel-plugin'.
So the stylesheet by itself produced a crash rather than an unstyled page. prepack now compiles dist/**/*.stylex.js as well. Only create, defineVars and keyframes are compile-time — props is a genuine runtime function, and a .svelte file reaches StyleX only through it — so compiling the 200 style modules is enough to make the whole package run with no compiler present.
That compile step checks itself, and the check earned its place immediately: the first run reported 26 classes that dist referenced but the stylesheet did not contain. StyleX derives defineVars companion classes from a module's path, and the two builds were compiling the same styles from different locations — src/lib/… for the stylesheet, dist/… for the modules. Compiling each module under its source identity closed the gap. The assertion stays, so that class of drift cannot ship quietly.
Verified the way a user would experience it: a Vite app with no StyleX plugin at all, built and driven in headless Chromium, renders an Avatar at border-radius: 9999px, 36×36, inline-flex.

A third oracle, and the bug it found

The port has always had two oracles diffing our compiled output against upstream's published packages. Both read .stylex.ts modules statically, which means neither can see inside a stylex.create function style — a style that takes an argument. There are 54 of them, and that blindness was documented but uncovered.
The new stylesheet gave us a way to cover it. compare-upstream-css.mjs diffs the generated astryx.css against upstream's published one, and a function style's output is just another rule there. Result: 1,463 shared atomic classes, zero differing rules.
It found a real bug on its first run.
Avatar's status dot — the little presence indicator — was positioned with a physical right, matching upstream 0.2.0. Upstream 0.3.0 had moved it to insetInlineEnd and mirrored the transform that pushes it onto the circle's edge:
ts
transform: {
default: 'translate(50%, 50%)',
':is([dir="rtl"] *)': 'translate(-50%, 50%)'
}
Ours had kept the old version, under a comment arguing for it. In right-to-left layouts the dot sat on the wrong side. The class oracle had reported zero mismatches the entire time, correctly — the bug was in a function style, where it could not look.

The cascade defect nobody could see

base.css declared four layers: reset, astryx-base, astryx-theme, product.
But compiling this package yourself emits nine — @layer priority1 through priority9 — and CSS layer order is order of first appearance. Nine layers nobody had named therefore sorted after product, inverting the whole cascade. Themes stopped being able to override components. Your app's CSS lost to component CSS it should have beaten. Silently, in every case.
The complete order had existed for months. It lived in this site's own app.html, where no consumer could benefit from it. base.css now names priority1…16 in the right position, verified in a real build rather than reasoned about: the declaration lands at byte 13,394, ahead of astryx-theme at 13,580.

Also in this release

  • Every package declares main. With exports present, Node and modern bundlers never read it, which is exactly why nobody noticed — but anything that does not understand exports could not resolve the package at all.
  • A source export condition on all nine non-CSS subpaths, so you can still compile from source and tree-shake if you would rather ship less CSS than the full 131 kB.
  • doctor accepts both routes. It gained a StyleX-wiring check in this release, and without this it would have told every correctly configured project that its components render unstyled.
  • Fixed: doctor reported optional peer dependencies as missing, telling projects on other bundlers to install two packages they have no use for.

Upgrading, and the one thing to watch

This is a delivery change, and a silent one if you miss it. A pre-compiled dist gives your StyleX plugin nothing to compile — so if you configured the compiler for 0.3.0, you will now get no component CSS.
Either add the stylesheet import:
ts
import '@astryx-svelte/core/astryx.css';
or keep compiling and ask for the new condition:
ts
// vite.config.ts
export default defineConfig({
plugins: [astryx(), sveltekit()],
resolve: { conditions: ['source'] }
});
This site was the first thing to hit it. Its CSS fell from ~250 kB to 161 kB with no error at all, which is a fair demonstration of why the failure mode is worth naming twice.
Release StyleX Parity

Related

Getting started
Styling
Components