sumi
Chapters

Snippets

A snippet is a chunk of template that a component renders on demand. Snippets are how a component takes markup from its caller — the role snippets play in Svelte 5 (which also replaced its slot mechanism with them) and children plays in React. Sumi had a slot mechanism; it was removed in favour of snippets, which are one idea covering both named and default content. This chapter is the full reference; components keeps a short overview that points here.

A snippet is represented as a Go closure returning child nodes:

func() []*sumi.Input

Everything below is that one type, produced and consumed in different places.

Local snippets

Inside a component, {snippet name()}…{/snippet} declares a reusable fragment and {render name()} renders it. This factors repeated markup without a separate component:

<script>
rows := sumi.New([]string{"a", "b"})
</script>

{snippet divider()}<hr />{/snippet}

<div>
    {render divider()}
    <div>{rows}</div>
    {render divider()}
</div>

Each local snippet compiles to a closure hoisted to component scope, so a single declaration can be rendered from several places. Parameters are ordinary Go and let one snippet vary per call:

{snippet cell(label string)}<td>{label}</td>{/snippet}

Snippet props

A component accepts a snippet from its caller by declaring a prop whose type is func() []*sumi.Input (optionally with parameters). The type is what marks it as a snippet — a prop is a snippet prop exactly when its Go type starts with func and ends with []*sumi.Input. The component renders it with {render} like a local one:

<!-- card.sumi -->
<script>
var title    string
var footer   func() []*sumi.Input
var children func() []*sumi.Input
</script>
<div class="card">
    <div class="card-title">{title}</div>
    <div class="card-body">{render children()}</div>
    <div class="card-foot">{render footer()}</div>
</div>

Passing snippets from the consumer

The caller fills those props from the component tag’s body. A {snippet name()}…{/snippet} block inside the tag becomes the matching named prop; everything else in the body becomes the implicit children snippet:

<Card title="Hi">
    <p>this goes to children</p>
    {snippet footer()}<p>this goes to footer</p>{/snippet}
</Card>

Here footer fills the footer prop and the <p>this goes to children</p> fills children. A component with no children prop simply ignores loose body content.

Ordinary props are still passed as attributes (title="Hi" above); snippet props come only from the body, never from an attribute.

Resolution and defaults

{render name} resolves against local snippets first, then snippet props, so a local {snippet} shadows a prop of the same name. A name that matches neither is a generation error:

{render foo} names an unknown snippet: declare a {snippet foo()} or a
snippet prop

A snippet prop the caller does not pass renders nothing — the compiler defaults an unpassed snippet prop to a closure returning nil, so {render footer()} on a <Card> with no footer produces empty output rather than a nil-pointer panic.

Migrating from slots

Slots were removed. The old <slot:name /> placeholder and {slot name}…{/slot} block now raise a generation error pointing at the replacement:

slots were removed; declare a {snippet name()} inside the component tag
and {render name()} in the component template

The translation is direct: a slot placeholder in the component becomes {render name()}, and the consumer’s slot-fill block becomes {snippet name()}…{/snippet} — with default (unnamed) content becoming loose body that lands in children.

Limitations

Two restrictions follow from snippets compiling to component-scope closures. Both surface as a Go compile error on the generated file, not a friendly diagnostic:

  • A snippet cannot capture a {for} variable. Because the closure is hoisted to component scope, a snippet declared inside a loop and referencing the loop variable emits code where that variable is out of scope (undefined: item). Render per-item markup inline in the loop instead.
  • A child component cannot appear inside a snippet body. Component instances are constructed at component scope before the tree is built, and that construction does not descend into snippet bodies, so a component tag there compiles to a reference to an uninstantiated variable (undefined: badge0). Plain HTML elements and control flow in a snippet body are fine.