How to nest partials and use slots

Partials compose freely. One partial can include another, and the caller can project content into the partial through <piko:slot>. This guide covers the patterns. See the PK file format reference for the full syntax.

Default slot

A partial with a bare <piko:slot /> accepts arbitrary content from its caller:

<!-- partials/card.pk -->
<template>
  <article class="card">
    <piko:slot />
  </article>
</template>

The caller fills the slot by placing content between the opening and closing <piko:partial> tags:

<piko:partial is="card">
  <h2>Title</h2>
  <p>Body</p>
</piko:partial>

Named slots

Use name on the slot to project different content into different regions:

<!-- partials/article.pk -->
<template>
  <article>
    <header>
      <piko:slot name="header" />
    </header>
    <main>
      <piko:slot />
    </main>
    <footer>
      <piko:slot name="footer" />
    </footer>
  </article>
</template>

The caller attaches content to a named slot with p-slot="name":

<piko:partial is="article">
  <h1 p-slot="header">My post</h1>
  <p>The body of the post.</p>
  <p p-slot="footer">Posted on 31 January 2026</p>
</piko:partial>

Content without a p-slot attribute flows into the default (unnamed) slot.

Slot fallback content

Place default content between <piko:slot> and </piko:slot>. It renders when the caller does not provide a slotted value:

<template>
  <div class="panel">
    <piko:slot>
      <p>No content provided.</p>
    </piko:slot>
  </div>
</template>

Nesting partials

A partial can use another partial inside its own template. Import the child and use <piko:partial is="child-alias">:

<!-- partials/dashboard.pk -->
<template>
  <div class="dashboard">
    <piko:partial is="card">
      <h3>Revenue</h3>
      <p>{{ state.Revenue }}</p>
    </piko:partial>

    <piko:partial is="card">
      <h3>Active users</h3>
      <p>{{ state.ActiveUsers }}</p>
    </piko:partial>
  </div>
</template>

<script type="application/x-go">
package main

import (
    "piko.sh/piko"
    card "myapp/partials/card.pk"
)

type Props struct {
    Revenue     string `prop:"revenue"`
    ActiveUsers int    `prop:"active_users"`
}

type Response struct {
    Revenue     string
    ActiveUsers int
}

func Render(r *piko.RequestData, props Props) (Response, piko.Metadata, error) {
    return Response{Revenue: props.Revenue, ActiveUsers: props.ActiveUsers}, piko.Metadata{}, nil
}
</script>

Each level adds its own scope. Nothing leaks out, and the composition remains type-checked.

Looping over partials

A p-for loop can render one partial per item:

<template>
  <div class="product-grid">
    <piko:partial
      p-for="product in state.Products"
      p-key="product.SKU"
      is="product-card"
      :sku="product.SKU"
      :name="product.Name"
      :price="product.Price" />
  </div>
</template>

Specify p-key when looping partials so Piko can keep identities stable across updates. The leading colon evaluates each value as an expression. Piko matches the bare attribute name against the Props struct of the product-card partial. See how to passing props to partials for the prefix-versus-bare attribute rules.

See also