Theme
Version
GitHub PyPI Discord
On this page

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:

Citry also has attributes for browser and server interaction:

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.