Slots¶
Slots let components wrap child content — the equivalent of React's children or Vue's <slot>.
Default slot¶
Content between {% component %} and {% endcomponent %} becomes the default slot:
{% component Card title="User profile" %}
<p>This paragraph is passed into the card.</p>
{% endcomponent %}
{# components/card.html #}
<article {% shard_root component %} class="card">
<h2>{{ props.title }}</h2>
<div class="card__body">{{ slots.default|safe }}</div>
</article>
Use |safe because slot content is pre-rendered HTML.
Named slots¶
For layout components with multiple insertion points:
{% component Layout %}
{% slot header %}
<h1>Dashboard</h1>
{% endslot %}
<p>Main content uses the default slot.</p>
{% slot footer %}
<small>© 2026</small>
{% endslot %}
{% endcomponent %}
{# components/layout.html #}
<div {% shard_root component %} class="layout">
<header>{{ slots.header|default:""|safe }}</header>
<main>{{ slots.default|safe }}</main>
<footer>{{ slots.footer|default:""|safe }}</footer>
</div>
Nesting components in slots¶
Slots can contain other components:
{% component Card title="Counter" %}
{% component Counter initial=3 %}{% endcomponent %}
{% endcomponent %}
Each nested component gets its own instance ID, state, and scoped styles.
Slots are persisted¶
Slot HTML is saved to cache with the component instance. HTMX re-renders include the original slot content automatically — actions don't need to know about slots.
Composition patterns¶
Wrapper components¶
Card, Button, Layout — structural components that accept arbitrary content:
class Button(Component):
variant = Prop(str, default="default")
template_name = "components/button.html"
Leaf components¶
Counter, TodoList — self-contained components with state and actions. Usually placed inside wrappers, not the other way around.
Slot-less components¶
Components without {{ slots.* }} in their template simply ignore wrapped content. Use self-closing syntax:
Empty slots¶
Check before rendering:
Or use the default filter: {{ slots.header|default:"" }}.