packages/components — verified on 0.2.321, also present in 0.2.252.
src/ui/switch/Switch.stx violates the rule the component docs themselves state, using this component's own line as the cautionary example.
The bug
// <script server>
const sizeConfig = {
sm: { track: 'h-5 w-9', thumb: 'h-4 w-4', translate: 'translate-x-4' },
md: { track: 'h-[38px] w-[74px]', thumb: 'h-[34px] w-[34px]', translate: 'translate-x-9' },
lg: { track: 'h-12 w-24', thumb: 'h-11 w-11', translate: 'translate-x-12' },
}
export const config = sizeConfig[size] // <- no fallback// <script client>
const config = {{ config }}size defaults to 'md', but any caller-supplied value outside sm | md | lg makes config undefined. The mustache then resolves to nothing, the braces survive into the browser, and the emitted client script contains a literal const config = [object Function] — a SyntaxError that kills the entire scoped script.
toggle() is defined in that script. So the failure mode is:
- the switch renders and looks completely normal
- clicking it does nothing, forever
@changenever fires- no error attributable to the component; the whole
<script client>block simply never ran
useModel, defineExpose and the emit all die with it.
Reproduction
<Switch size="medium" :checked="on" @change="save()" />
<Switch size="small" :checked="on" @change="save()" />
<Switch size="large" :checked="on" @change="save()" />Each renders; none toggles. sm/md/lg work, so a caller writing the obvious long-form spelling gets a control that is inert with no diagnostic.
This is the documented failure mode
docs/guide/using-components.md states the rule, and gives the fix using this exact identifier:
A mustache there is a server value spliced into JavaScript. If it resolves to
undefinedthe braces survive into the browser, andconst n =is a SyntaxError that kills the whole scoped script — every handler in it, with no message.export const config = sizeConfig[size] || sizeConfig.md // a typo'd size is not a reason to die
The docs are right and the component does not do it.
Fix
export const config = sizeConfig[size] || sizeConfig.mdI scanned all 102 components for the same shape — a bare map[key] export spliced into <script client> — and Switch is the only one, so this is a one-line fix rather than a sweep. (Button, TextInput, PasswordInput and Progress have unguarded lookups too, but theirs are interpolated in the template, where undefined yields an empty class attribute rather than a SyntaxError. Cosmetic, worth tightening, not urgent.)
Second, separate point: the md track is unthemeable
Not a crash, but it is why we did not adopt Switch anywhere:
mdis a 74×38px track. That is very large for a settings row; our apps use 38×22.- on/off colours are hard-coded
bg-blue-500 dark:bg-blue-600/bg-neutral-400 dark:bg-neutral-600, not tokens.
There is no prop for either, so matching a product's design system means restyling the control to match the component rather than the other way round. A --stx-switch-* custom-property hook, or letting className reach the track and thumb, would make it adoptable. Happy to open that as its own issue if you would rather keep this one to the crash.