Summary
A component whose template uses a scoped slot — <slot :name="value" /> rather than <slot /> — silently drops everything the caller passed into it, and emits a literal, malformed <slot … / /> element into the HTML.
Nine shipped components in @stacksjs/components use this form, so nine components render as empty shells. <Table> is the clearest case: it renders <table> with nothing in it, whatever you put inside.
Minimal reproduction
Two components, identical except for the slot:
<!-- plain-slot.stx -->
<script server>
export const tone = $props.tone || 'a'
</script>
<div data-plain=""><slot /></div><!-- scoped-slot.stx -->
<script server>
export const tone = $props.tone || 'a'
</script>
<div data-scoped=""><slot :tone="tone" /></div>Called the same way:
<PlainSlot tone="x">PLAIN-CHILD-RENDERED</PlainSlot>
<ScopedSlot tone="x">SCOPED-CHILD-RENDERED</ScopedSlot>Rendered output:
<div data-plain="x">PLAIN-CHILD-RENDERED</div>
<div data-scoped="x"><slot tone="x" / /></div>The binding was evaluated — :tone="tone" became tone="x" — but the slot was never substituted, the children are gone, and the emitted tag has a stray / before the self-closing />.
Impact: <Table> renders nothing
packages/components/src/ui/table/Table.stx ends with:
<table class="">
<slot :striped="striped" :hoverable="hoverable" />
</table>So this:
<Table striped="true">
<TableHead><TableRow><TableHeader>Date</TableHeader><TableHeader>Session</TableHeader></TableRow></TableHead>
<TableBody><TableRow><TableCell>Mon</TableCell><TableCell>Intervals</TableCell></TableRow></TableBody>
</Table>produces:
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-line">
<slot striped="true" hoverable="true" / />
</table>
</div>document.querySelectorAll('table')[0].rows.length → 0.
Dropping the <Table> wrapper and using a plain <table> with the same children works — 2 rows, 2 cells each — which confirms the sub-components are fine and the wrapper's slot is what fails.
The same thing removes every <DropdownItem>'s content: two items in, two literal <slot / /> out, no labels.
Affected components
Every file matching <slot : in packages/components/src:
| File | What breaks |
|---|---|
ui/table/Table.stx | the whole Table/TableHead/TableBody/TableRow/TableHeader/TableCell family |
ui/dropdown/DropdownItem.stx | menu item labels |
ui/listbox/ListboxOption.stx | option labels |
ui/combobox/ComboboxOption.stx | option labels |
ui/radio-group/RadioGroupOption.stx | option labels |
ui/stepper/Stepper.stx, ui/stepper/StepperStep.stx | step content |
ui/notification/Notification.stx | body content |
ui/virtual-list/VirtualList.stx | every row |
Expected
Either the renderer substitutes scoped slots (exposing the bound values to the slotted content), or — if scoped slots are not a supported feature — the nine components should use a plain <slot /> and pass their values another way, so that at minimum children are not discarded and no literal <slot> reaches the browser.
Silently emitting <slot … / /> is the worst of the three outcomes: nothing renders and nothing says why.
Environment
- stx /
@stacksjs/components0.2.339 - stacks 0.75.31, Bun 1.3.x, macOS
- Found while migrating hq.training onto the component library.