Button: a reactive expression in the slot renders to nothing, so the button ships with no text — the :label fix for #2034 does not cover it.
#2034 and its fix in 0.2.384 address the :label prop and add ariaLabel for a button that has no slot content. There is a third shape that still fails, and it is the one that reads most naturally:
<Button type="submit" :loading="loading()" fullWidth>
{{ loading() ? 'Signing in…' : 'Sign in' }}
</Button>The slot is the caller's markup interpolated in the caller's scope at render time, and loading is a client signal, so the expression resolves to nothing. The served button is:
<button type="submit" class="inline-flex items-center justify-center … w-full btn-accent" aria-busy="false" …>
<svg class="animate-spin h-5 w-5" style="display:none">…</svg>
</button>A spinner and no text. textContent is whitespace, there is no aria-label, and it stays that way after hydration — the slot was never emitted, so there is nothing for the runtime to fill in. Six call sites across one app's auth pages shipped like that: the sign-in, sign-up, password-reset and create-app buttons all had no accessible name and nothing visible on them.
Verified identical on 0.2.382 and 0.2.395, rebuilding in between.
Why it is easy to land on
The component's own comment says the slot "cannot express" a state-dependent label and points at :label. That is correct, but nothing tells the caller: the expression is valid stx, the build reports success, typecheck and lint pass, and the page renders. The only gate that caught it was an accessibility audit noticing an unnamed control.
The contrast that makes it confusing is that a native element in the same file works:
<button :disabled="loading()">{{ loading() ? 'Redirecting…' : 'Upgrade to Pro' }}</button>There the literal is emitted as text and hydration replaces it, so the page is correct. Same expression, same scope, opposite outcome, depending only on whether the tag is a component.
Suggested fix
Either of these would do, in order of preference:
- Emit the slot's literal expression as text the way a native element does, so hydration can replace it. That removes the asymmetry entirely and makes the natural spelling correct.
- Warn at render when a component's slot content contains an unresolved
after interpolation — it means the caller referenced something the server could not see, which is almost always a client signal and almost never intended.
Even just (2) would have turned six silently broken production buttons into a build-time message.
Found on 0.2.382, still present on 0.2.395. Related: #2034, #2037.