Conditionals
if / elif / else
{% if user.is_admin %}
<span class="badge">Admin</span>
{% elif user.is_moderator %}
<span class="badge">Mod</span>
{% else %}
<span class="badge">User</span>
{% end %}
Note: Kida uses unified {% end %}to close all blocks.
Inline Conditionals
{{ "Active" if is_active else "Inactive" }}
Boolean Operators
{% if user and user.is_active %}
{% if not disabled %}
{% if a or b %}
{% if (a and b) or c %}
Comparison Operators
{% if count > 0 %}
{% if status == "active" %}
{% if value in allowed_values %}
{% if item is defined %}
Loops
for Loop
{% for item in items %}
<li>{{ item.name }}</li>
{% end %}
Loop with else
Theelseblock runs when the sequence is empty:
{% for item in items %}
<li>{{ item.name }}</li>
{% else %}
<li>No items found</li>
{% end %}
Loop Context
Access loop state via theloopvariable:
{% for item in items %}
{{ loop.index }} {# 1-based index #}
{{ loop.index0 }} {# 0-based index #}
{{ loop.first }} {# True on first iteration #}
{{ loop.last }} {# True on last iteration #}
{{ loop.length }} {# Total items #}
{{ loop.revindex }} {# Reverse index (1-based) #}
{{ loop.revindex0 }} {# Reverse index (0-based) #}
{% end %}
Unpacking
{% for key, value in data.items() %}
{{ key }}: {{ value }}
{% end %}
{% for x, y, z in coordinates %}
Point: ({{ x }}, {{ y }}, {{ z }})
{% end %}
Filtering Items
{% for user in users if user.is_active %}
{{ user.name }}
{% end %}
Pattern Matching
Kida adds{% match %}for cleaner branching:
{% match status %}
{% case "active" %}
✓ Active
{% case "pending" %}
⏳ Pending
{% case "error" %}
✗ Error: {{ error_message }}
{% case _ %}
Unknown status
{% end %}
Match with Guards
{% match user %}
{% case {"role": "admin"} %}
Full access
{% case {"role": "user", "verified": true} %}
Standard access
{% case _ %}
Limited access
{% end %}
Variables
Kida has three scoping keywords. Understanding the difference is critical when migrating from Jinja2.
let
Template-wide variable — visible everywhere after assignment:
{% let name = "Alice" %}
{% let items = [1, 2, 3] %}
{% let total = price * quantity %}
{{ name }} → Alice
This is the closest equivalent to Jinja2's {% set %}.
set
Block-scoped variable — does not leak out of{% if %}, {% for %}, or other blocks:
{% let x = "outer" %}
{% if true %}
{% set x = "inner" %}
{{ x }} → inner
{% end %}
{{ x }} → outer {# set did not modify the outer variable #}
Coming from Jinja2? This is the biggest scoping difference. In Jinja2,
setinside a block modifies the outer variable. In Kida,setis block-scoped. Useletfor template-wide variables orexportto push values out of blocks.
export / promote
Promotes a variable to the template (outermost) scope — it escapes all nested blocks, not just the immediate parent:
{% let total = 0 %}
{% for item in items %}
{% export total = total + item.price %}
{% end %}
{{ total }} → sum of all prices
promote is an alias for export— use whichever reads better:
{% for item in items %}
{% promote first_item ??= item %}
{% end %}
{{ first_item }} → first item in the list
This replaces Jinja2's namespace()pattern for accumulating values inside loops.
Note:
export/promotealways writes to the template scope, even from deeply nested blocks. Anexportinside anifinside aforwill escape both.
Nullish Assignment (??=)
Use??=to assign only when a variable is undefined or None. Works with all three
scoping keywords:
{# Set defaults without overwriting existing values #}
{% let title ??= "Untitled" %}
{% let show_sidebar ??= true %}
{# In loops — capture only the first value #}
{% for item in items %}
{% promote winner ??= item %}
{% end %}
{{ winner }} → first item (??= is a no-op after that)
{# With context variables — respect what the caller passed #}
{% let page_class ??= "default" %}
??= checks for both undefined and None. It does not treat falsy values
like0, "", or False as missing — only Noneand truly undefined variables.
Whitespace Control
Trim whitespace with-:
{%- if true -%}
trimmed
{%- end -%}
{%-trims whitespace before the tag-%}trims whitespace after the tag{{-trims whitespace before the expression-}}trims whitespace after the expression
Whitespace Trim vs Negative Numbers
The-modifier must appear immediately after the delimiter with no space.
A space before-makes it a unary minus (negative number):
{{-5}} {# Whitespace trim + outputs 5 #}
{{ -5 }} {# Outputs -5 (negative number) #}
{{ - 5 }} {# Also outputs -5 #}
When outputting a negative literal, always include a space after {{:
{# Correct — negative number #}
{{ -5 }}
{# Surprising — trims whitespace and outputs 5, not -5 #}
{{-5}}
The same applies to block tags:
{%- set x = -5 %} {# Trims whitespace, assigns -5 #}
{% set x = -5 %} {# No trim, assigns -5 #}