{% provide %} and consume()let a parent push state that any descendant
can read -- across slot boundaries, includes, and imported macros -- without
passing it through every layer of parameters.
Quick start
{% provide theme = "dark" %}
{{ consume("theme") }} {# "dark" #}
{% endprovide %}
provide pushes a value onto a per-key stack. consumereads the top of
that stack, or returns a default if the key is absent.
Syntax
provide
{% provide key = expr %}
...body...
{% endprovide %}
- key -- a plain name (not a string).
- expr -- any expression: a literal, variable, function call, etc.
- The body can contain any template content.
{% end %}is accepted as a shorthand for{% endprovide %}.
consume
{{ consume("key") }}
{{ consume("key", default) }}
- key -- a string naming the provided value.
- default -- returned when no provider for that key is active. Defaults
to
Nonewhen omitted.
Nesting and shadowing
Providers nest. An innerprovidewith the same key shadows the outer one
for the duration of its body, then the outer value is automatically restored:
{% provide color = "red" %}
{{ consume("color") }} {# "red" #}
{% provide color = "blue" %}
{{ consume("color") }} {# "blue" #}
{% end %}
{{ consume("color") }} {# "red" again #}
{% end %}
Independent keys coexist without interference:
{% provide a = 1 %}
{% provide b = 2 %}
{{ consume("a") }}, {{ consume("b") }} {# 1, 2 #}
{% end %}
{% end %}
Components: table + row
The motivating use case is implicit configuration between paired components.
Atable provides alignment metadata that rowconsumes, so callers never
pass alignment to every row by hand:
{# components/table.html #}
{% def table(headers, align=none) %}
{% provide _table_align = align %}
<table>
<thead>
<tr>{% for h in headers %}<th>{{ h }}</th>{% end %}</tr>
</thead>
<tbody>{% slot %}</tbody>
</table>
{% endprovide %}
{% end %}
{% def row(*cells) %}
{% set align = consume("_table_align") %}
<tr>
{% for cell in cells %}
<td{% if align %} class="align-{{ align[loop.index0] }}"{% endif %}>
{{ cell }}
</td>
{% end %}
</tr>
{% end %}
Usage:
{% from "components/table.html" import table, row %}
{% call table(headers=["Name", "Count"], align=["left", "right"]) %}
{{ row("Alice", "42") }}
{{ row("Bob", "17") }}
{% end %}
rownever receives alignment as a parameter -- it reads it from the
nearesttable ancestor via consume.
Theme provider pattern
Another common pattern: a theme wrapper that sets context for all descendant components.
{% def theme_provider(name="light") %}
{% provide theme = name %}
<div class="theme-{{ name }}">{% slot %}</div>
{% endprovide %}
{% end %}
{% def button(label) %}
<button class="btn btn-{{ consume("theme", "light") }}">{{ label }}</button>
{% end %}
Nested providers override the theme for their subtree:
{% call theme_provider("dark") %}
{{ button("Save") }} {# btn-dark #}
{% call theme_provider("accent") %}
{{ button("Upgrade") }} {# btn-accent #}
{% end %}
{{ button("Cancel") }} {# btn-dark (restored) #}
{% end %}
Where consume works
Provided values are visible everywhere the render context is shared:
| Boundary | Visible? |
|---|---|
Slot content ({% call %}...{% end %}) |
Yes |
Macro calls ({{ child() }}) |
Yes |
Imported macros ({% from "x.html" import y %}) |
Yes |
Includes ({% include "x.html" %}) |
Yes |
| Child templates (inheritance) | Yes |
Error safety
provide compiles to a try/finallyblock, so the stack is always
cleaned up -- even when the body raises an exception. After an error, a
fresh render starts with a clean provider state.
Naming conventions
Use an underscore prefix for keys that are internal to a component pair:
{% provide _table_align = align %} {# internal to table/row #}
Use plain names for user-facing configuration:
{% provide theme = "dark" %}
Example
A full working example is in
examples/provide_consume/.