What happens
class="text-xs btn-primary" renders at text-sm. So does class="btn-primary text-xs". The utility never applies, in either order.
The same is true of colours: with
shortcuts: {
'btn-primary': 'inline-flex px-4 py-2 font-semibold text-sm text-white bg-blue-600 … rounded-xl',
}class="text-white bg-rose-600 hover:bg-rose-700 btn-primary" renders blue.
Why
The shortcut is emitted twice in the generated stylesheet. In one app's _stx/css.*.css:
.btn-primary { … } at byte 19498
.text-xs { … } at byte 33120
.btn-primary { … } at byte 42982 ← again, after every utilityBoth are single-class selectors, so specificity ties and source order decides. The second copy of the shortcut is last, so the shortcut always wins.
The duplicate looks like two different emitters: the first copy is pretty-printed, the second is on one line.
.btn-danger-ghost {\n display: inline-flex;\n align-items: center;\n …
.btn-danger-ghost { display: inline-flex; align-items: center; …Why this is worth fixing rather than documenting
It fails silently and it fails plausibly. There is no build error, no console warning, and the element still renders as a button — just not the one the source describes. Nothing about reading the template tells you the utility is dead.
In the app that found this, it had produced:
- 13 destructive buttons drawn as safe ones. Every "Delete this workout", "Delete plan", "Confirm delete", "Remove", "Disconnect", "Stop sharing" and "Cancel membership" was written with rose over a
btn-primary/btn-ghostshortcut and rendered in the primary blue or plain slate — a delete confirmation styled identically to Save, sitting right next to it. - 36 buttons meant to be small that are not.
text-xsnext to a shortcut, across 14 files, none of it applying. Confirmed againsttext-smandtext-[11px]too; only!text-xswins.
Nobody noticed either, for as long as the code has existed, which is the point: the failure mode is a button that looks deliberate.
Expectation
A utility written alongside a shortcut should win, the way @apply-style shortcuts behave in Tailwind and UnoCSS — the shortcut is the base, the utility is the override. That is what every call site here was written assuming.
At minimum, the shortcut should be emitted once. If the duplicate is intentional (safelist re-emission?), the second copy could be dropped when the first is already present.
Reproducing
<button class="text-xs btn-primary">small?</button>
<button class="btn-primary text-xs">small?</button>
<button class="!text-xs btn-primary">small</button>getComputedStyle(...).fontSize → 14px, 14px, 12px.
Measured on @stacksjs/stx 0.2.339 / crosswind as shipped with stacks 0.75.34, in a browser against the served stylesheet.