Slots

A slot is a hole in a component that the parent fills with its own HTML.

Props pass data down; slots pass markup down. That is what lets one component wrap content it knows nothing about.

Default Slot

Put <slot /> in the component, where the content should land:

<!-- Card.html -->
<div class="card">
  <slot />
</div>

Whatever you write between the component's tags fills that slot:

source
<Card>
  <strong>Storage</strong>
  <p>You have used 4.2 GB of your 10 GB plan.</p>
</Card>
output
Storage

You have used 4.2 GB of your 10 GB plan.

Default Content

Content written inside <slot> is a fallback. It renders only when the parent passes nothing:

<!-- Button.html -->
<button class="btn">
  <slot>Click me</slot>
</button>
source
<!-- Nothing passed, so the fallback shows -->
<Button />

<!-- Content passed, so it replaces the fallback -->
<Button>Submit</Button>
output

Named Slots

Give a slot a name when a component has more than one hole. Here is the same card, grown to a header, a body and a footer:

<!-- Card.html -->
<div class="card">
  <header class="card-header">
    <slot name="header">Untitled</slot>
  </header>

  <div class="card-body">
    <slot />
  </div>

  <footer class="card-footer">
    <slot name="footer" />
  </footer>
</div>

The parent aims content at a named slot with the slot attribute. Anything without one goes to the unnamed slot:

source
<Card>
  <span slot="header">Storage</span>

  <p>You have used 4.2 GB of your 10 GB plan.</p>

  <span slot="footer">Updated a moment ago</span>
</Card>
output
Storage

You have used 4.2 GB of your 10 GB plan.

Updated a moment ago

Modals, drawers and popovers are built in and already handle layering, focus and dismissal, so reach for those rather than building your own from slots - see Overlays & UI State.

Multiple Elements to Same Slot

Several elements can target one slot. They arrive in the order you wrote them:

source
<Card>
  <span slot="header">Storage</span>
  <span slot="header">Pro plan</span>

  <p>You have used 4.2 GB of your 10 GB plan.</p>

  <span slot="footer">Updated a moment ago</span>
</Card>
output
StoragePro plan

You have used 4.2 GB of your 10 GB plan.

Updated a moment ago

Slot Props

A slot can hand values out to the content that fills it. Write them as shorthand attributes on the <slot> tag:

source
<!-- Card.html -->
<script>
let plan = 'Pro'
</script>

<div class="card">
  <header class="card-header">
    <slot name="header" {plan}  />
  </header>

  <div class="card-body">
    <slot />
  </div>
</div>

Any component placed in that slot receives them as ordinary props:

source
<Card>
  <!-- PlanBadge receives plan="Pro" from the header slot -->
  <PlanBadge slot="header" />

  <p>You have used 4.2 GB of your 10 GB plan.</p>
</Card>

What a slot offers reaches that slot's content, and nothing else in the file. Inside it, two things receive the value:

  • A component projected into the slot. <PlanBadge /> gets plan as if it had been written on the tag, so a let plan it declares is filled in rather than defaulted.
  • An expression written in the content. {plan} beside the tag reads the same value, because the offer is layered over your own names for the length of that slot's content.

Changed in 0.1. An offer used to reach component tags only, so an expression that named one rendered empty. Both halves are compiled now.

A value written at the call site (<PlanBadge plan="Free" />) wins over the offer, and a name the declaring component has no value for leaves the consumer's own default alone.

Unlike ordinary props they keep their JavaScript type instead of arriving as strings. Every other prop is an attribute and therefore a string; this one is the declaring component's own value, so a number arrives as a number, an array as an array, and a function as something the content can call. Calling one is the usual way a projected component talks back - an <Accordion> that offers {open} and {toggle} lets its projected header drive it.

It is a snapshot, not a link. The value is read where the content renders. If the declaring component changes it afterwards, what the content received does not follow. Where the content has to track a value, render it inside the component that owns it.

What Projected Content Resolves Against

Content you write inside a component tag is yours. It is compiled as part of the template that wrote it, and it lands in the child's <slot /> position - so its names are your component's names, not the child's.

In projected content What happens
{count} and other text expressions Resolve against your state
An inline handler, onclick="save()" Compiles into your handler table
An expression prop on a projected component, <Tally total={count} /> An ordinary reactive prop: it updates when count does. Changed in 0.1 - it used to be interpolated once at mount and never again
A <for> or <show> over your own state Renders your list, in the child's element. Changed in 0.1 - it used to be mounted by the CHILD, against state where your variable did not exist, so it silently rendered nothing
A name the slot offers, {plan} inside the content of <slot {plan} /> The declaring component's value, ahead of a name of your own spelled the same way. Changed in 0.1 - an offer used to reach component tags only, so an expression naming one rendered empty
An attribute expression on an element, <p class="{cls}"> A build error. Put the element in a component of your own, or pass the whole class string as a prop
A directive - class:, style:, bind:, ref:, on:, use: Dropped, with a [jaf] warning naming it
Tip: Slots are HTML-level, not script-level: a component cannot read its own slot content from JavaScript. When a region might be empty, give its slot sensible default content instead of trying to detect what the parent passed.