Skip to content

Partials

A partial renders a separately-loaded template in place of a {{> key }} tag. Which template answers a given key is configured by whoever builds the application you're writing templates for — see Custom Partials if that's you. From a template's point of view, a partial is resolved once per distinct key per render and its result substituted in place, as if that other template's text had been written there directly.

{{#each member}}- {{> memberInfo }}
{{/each}}

Context

By default a partial renders against the current context, so a partial used inside {{#each}} sees the element the loop pushed. A partial key is either:

  • a static symbol written literally in the template — {{> memberInfo }}; or
  • a parenthesized expression evaluated at render time and coerced to a string — {{> (which) }}. Whitespace immediately inside the parentheses is optional: {{> (which) }} and {{> ( which ) }} both resolve the same key.

An optional context argument pushes a new scope for the partial, like {{#with}}:

{{> memberInfo spouse }}

Here the partial renders against the value of spouse instead of the current context.

Failure behavior

Partials follow the kBars rule of thumb — mistakes knowable when the template is written fail hard, data-driven mistakes fail soft:

Situation Result
No partial resolver configured for the application, but a {{> … }} tag is present hard failure
Static symbol key the application doesn't recognize hard failure
Dynamic key that resolves to an unknown key soft fail: emits nothing
Recursion deeper than the application's configured ceiling (default 64) soft fail: emits nothing

Partial parameters (from Handlebars) are intentionally not supported.

Inline partials

{{#inline name }} … {{/inline}} defines a partial body inline, right in the template:

{{#inline memberInfo}}
- {{ name }} ({{ role }})
{{/inline}}
{{#each member}}{{> memberInfo }}
{{/each}}

The name is a bare identifier, not a quoted string (kBars has no {{#*inline "name"}} form). The block itself renders nothing; it only registers name so a {{> name }} reference — static or dynamic — can invoke it, exactly like a loaded partial.

An inline definition is block-scoped: it is visible for the rest of the block it is defined in (the enclosing template, {{#each}}/{{#if}}/{{#with}} body, or partial body), including textually before its own definition — the whole block is scanned for definitions before anything in it renders — and inside any partial invoked from within that block. It goes out of scope once rendering leaves the enclosing block, so a sibling block cannot see a definition made in another one.

If an inline definition and a loadable partial share a name, the inline definition wins — the loader is never consulted for that name while the inline definition is in scope. This also means an inline partial renders even when no loader is configured at all; the "no loader" hard failure only fires when a static key matches neither an inline definition nor a loader.

Defining the same name twice in one block is last-wins — the later {{#inline}} block is the one used — and reports a soft failure (KB-5008) each time the enclosing block renders, since it usually indicates a template bug even though rendering proceeds.

An inline partial's body is subject to the same recursion-depth ceiling as a loaded partial — a self-referential inline partial soft-fails once that ceiling is reached, same as recursion through the loader.

Partial blocks

{{#> key }} … {{/>}} (bare close) or {{#> key }} … {{/> key }} (named close) is a partial block: key resolves exactly like a bare {{> key }} reference — a static symbol, a dynamic (expr), and an optional trailing context argument all work the same way — but a resolution miss renders the block's own body as fallback content instead of failing:

{{#> memberInfo }}
  No member info available.
{{/>}}

If memberInfo resolves (to an in-scope inline definition or a loadable partial), that partial renders and the fallback is discarded. If it resolves to nothing — no inline definition, an unregistered static key, an unresolved dynamic key, or no loader configured at all — the fallback renders instead, silently: no hard failure and no soft-fail event. The fallback is the author's handling of the miss.

This is the failover half of Handlebars' partial blocks and only that half: kBars has no {{> @partial-block }} re-entry, so an invoked partial cannot pull the fallback body back in.

Closing forms

{{#> memberInfo }} … {{/>}}             ← bare close: closes whatever partial block is open, no name check
{{#> memberInfo }} … {{/> memberInfo }} ← named close: the name must equal the open key's literal text

A named close whose name differs from the open key is a hard parse-time authoring error, KB-4003 (PartialBlockCloseMismatch). A named close only makes sense paired with a symbol open key — a dynamic open key's literal text (e.g. (which)) can never equal a bare identifier close name, so pairing a named close with a dynamic open always fails with KB-4003; use the bare {{/>}} form for a dynamic key.

Context argument and fallback scope

A context argument on the open tag ({{#> memberInfo person }}) applies only to the resolved partial on a hit, exactly like {{> memberInfo person }}. The fallback is inline template content, not a partial invocation: it renders against the current context, gains no recursion-depth increment, and adds no frame to the template stack. A self-referential fallback is therefore ordinary block nesting, not partial recursion.

Where to next