Skip to content

Jinja macros

{% from "ads/form.html" import button, checkbox, field, select, textarea %}

Vendored into templates/ads/ by abilian-ds sync, and hashed by the linter so a local edit is a finding rather than a silent fork.

Not usable inside a situ component

situ compiles a component's .html at source level, so a binder a macro emits is never seen by the compiler. Macros render server-side chrome; islands are hand-written.

field()

{{ field("email", label="Email", type="email", value=user.email,
         help="We only use this for sign-in.",
         errors=form.errors.get("email"),
         required=true, placeholder="you@example.com", class="") }}
argument default
name becomes the id, the name, and the stem of the help/error ids
label
type "text" any text-like input type
value ""
help none rendered as .ads-help, wired to aria-describedby
errors none a list of strings; joined, and sets aria-invalid="true"
required false sets required and the label's asterisk
placeholder none
class "" appended to the field wrapper, for layout at the call site

select()

{{ select("role", label="Role", value=user.role,
          options=[("admin", "Administrator"), ("editor", "Editor")]) }}

options is a list of (value, label) pairs. Otherwise the same contract as field().

textarea()

{{ textarea("bio", label="Bio", rows=6, value=user.bio) }}

Same contract as field(), plus rows (default 4). The value is content, not an attribute, so newlines survive.

checkbox()

{{ checkbox("digest", label="Send me the weekly digest", checked=user.digest) }}

The input sits inside the label, so the association is implicit and needs no for.

button()

{{ button("Save", type="submit") }}
{{ button("Cancel", variant="secondary") }}
{{ button("Delete", variant="danger", size="s") }}
argument default values
label
variant "primary" primary · secondary · ghost · danger
size "m" s · m
type "button"
disabled false
class ""

With undefined=StrictUndefined, a misspelt variant raises at render time rather than producing an unstyled button. That is the entire reason the setting is mandatory.

What is not here

menu() and a helper that turned unknown keyword arguments into dashed HTML attributes (hx_post=hx-post=) existed in the Tailwind-era package and were removed rather than ported. The classes a menu needs are in the system — ads-dropdown, ads-menu, ads-menu__item — so the markup is a few lines:

<details class="ads-dropdown">
  <summary class="ads-btn">Actions</summary>
  <div class="ads-menu">
    <a class="ads-menu__item" href="/edit">Edit</a>
    <button class="ads-menu__item ads-tone-danger">Delete</button>
  </div>
</details>