Template basics
A Citry template turns component data into the page people see. You can insert names and calculated values, show or hide content, repeat an element for every item in a collection, set HTML attributes from data, and build a page from smaller components.
Citry keeps those jobs close to ordinary HTML. {{ ... }} inserts a Python value, c-* attributes let Python decide how an element renders, and <c-*> tags place components and built-in behavior in the page.
<!-- heading = "Reading list"
books = ["Dune", "Kindred"] -->
<section c-class="['shelf', {'has-books': books}]">
<h1>
{{ heading }}
</h1>
<p c-for="book in books">
{{ book }}
</p>
<p c-empty>
No books yet.
</p>
</section>
Citry evaluates the Python expressions before the HTML reaches the browser. This example inserts the heading, adds the has-books class, and creates one paragraph for every book.
When part of the page should respond immediately to a click, keystroke, or other browser action, use Alpine in the component's HTML. Its x-data, x-show, and @click attributes run after the page loads, without asking Python to render the page again. Start with Alpine in templates.
The syntax at a glance
Core features:
{{ expression }}: insert a Python valuec-title="heading": set a Python value as attribute or component inputc-if,c-for: conditions and loopsx-*,@event,:name: Alpine behavior<c-Card>,<c-slot>: components and built-in tags{# ... #},<c-raw>: comments or literal textc-body="<>...</>": markup through an attribute
Citry also has attributes for browser and server interaction:
$c-props,@c-*, and:c-*are covered in Client interactivity and Events#c-keyand#c-ignoreare the template's template flags. They guide how an event response updates existing HTML. See also Event actions.
Self-closing tags
Opening and closing tags must match (case-insensitive). Standard void elements such as <input> and <br> do not need closing tags. Other tags may use the compact self-closing form too:
<input name="query">
<span />
<c-StatusBadge />
When rendered, <span /> becomes <span></span>.
Citry component syntax always starts with the exact lowercase c- prefix. The component name after it is case-insensitive, so <c-StatusBadge> and <c-statusbadge> find the same registration. Structural tags such as <c-if>, <c-for>, and <c-slot> must use their lowercase spelling; <c-If> is an error, not an alias.
Expressions
The {{ ... }} Python expressions are allowed only outside of tags:
{# ✅ Valid #}
<p title="Some title">
{{ content }}
</p>
{# ❌ Invalid #}
<p title="{{ content }}">
</p>
{# ❌ Invalid #}
<p title="Some title" {{ content }}>
</p>
{# ❌ Invalid #}
<{{ tag }} title="Some title">
</{{ tag }}>
Python attributes
To use Python in HTML attributes, prefix the name with c-:
{# Static title #}
<p title="My Title">
{# Dynamic title #}
<p c-title="heading.upper()">
Static HTML attributes are literal strings.
Citry strips the c- prefix from the dynamic attributes, so:
<p c-title="heading.upper()">
becomes:
<p title="MY TITLE">
Note
Attribute values may use double quotes, single quotes, or HTML's unquoted form. Always quote dynamic c-* expressions so spaces and operators stay inside the value.
Boolean attributes
A value-less HTML attribute is a bare boolean attribute. On a component tag, the same spelling passes the Python value True:
<input required>
<c-Button compact />
Other
HTML comments, declarations such as <!doctype html>, and processing instructions remain part of the output. Citry does not use Django or Jinja block syntax, so text such as {% include "menu.html" %} stays literal too.
Choose the next page
Start with Expressions if you want to insert or compute a value. Continue to Attributes when that value belongs on an HTML element or needs to become a Python input to a component.