Template Syntax

Spry extends HTML with special attributes that enable reactive behavior, component composition, and dynamic content. This page covers all the template syntax available in Spry components.

Special Attributes

sid - Spry ID

The sid attribute assigns a unique identifier to an element within a component. Use it to select and manipulate elements in your Vala code.

HTML
<div sid="container">
    <span sid="title">Hello</span>
    <button sid="submit-btn">Submit</button>
</div>
Vala
// Access elements by sid in prepare() or handle_action()
this["title"].text_content = "Updated Title";
this["submit-btn"].set_attribute("disabled", "true");

spry-action - HTMX Action Binding

The spry-action attribute binds an element to trigger a component action when interacted with. The action is sent to the server via HTMX.

HTML
<!-- Same-component action -->
<button spry-action=":Toggle">Toggle</button>

<!-- Cross-component action -->
<button spry-action="ListComponent:Add">Add Item</button>

See the Actions page for more details.

spry-target - Scoped Targeting

The spry-target attribute specifies which element should be replaced when an action completes. It targets elements by their sid within the same component.

HTML
<div sid="item" class="item">
    <span sid="title">Item Title</span>
    <button spry-action=":Delete" spry-target="item">Delete</button>
</div>

spry-global - Out-of-Band Swaps

The spry-global attribute marks an element for out-of-band updates. When included in a response, HTMX will swap this element anywhere on the page that has a matching id.

HTML
<!-- In HeaderComponent -->
<div id="header" spry-global="header">
    <h1>Todo App</h1>
    <span sid="count">0 items</span>
</div>
Vala
// In another component's handle_action()
add_globals_from(header);  // Includes header in response for OOB swap

*-expr - Expression Attributes

Expression attributes dynamically set HTML attributes based on component properties. Use content-expr for text content, class-*-expr for conditional classes, and any-attr-expr for any other attribute.

HTML
<!-- Content expression -->
<span content-expr='format("%i%%", this.percent)'>0%</span>

<!-- Style expression -->
<div style-width-expr='format("%i%%", this.percent)'></div>

<!-- Conditional class -->
<div class-completed-expr="this.is_completed">Task</div>

<!-- Any attribute expression -->
<input disabled-expr="this.is_readonly">

spry-if / spry-else-if / spry-else - Conditionals

Use these attributes for conditional rendering. Only elements with truthy conditions will be included in the output.

HTML
<div spry-if="this.is_admin">Admin Panel</div>
<div spry-else-if="this.is_moderator">Moderator Panel</div>
<div spry-else>User Panel</div>

spry-per-* - Loop Rendering

Use spry-per-{varname}="expression" to iterate over collections. The variable name becomes available in nested expressions.

HTML
<ul>
    <li spry-per-task="this.tasks">
        <span content-expr="task.name"></span>
        <span content-expr="task.status"></span>
    </li>
</ul>

Special Elements

<spry-outlet> - Child Component Outlets

Outlets are placeholders where child components can be inserted dynamically. Use set_outlet_children() to populate outlets.

HTML
<div sid="list">
    <spry-outlet sid="items"/>
</div>

See the Outlets page for more details.

<spry-component> - Declarative Child Components

Use <spry-component> to declare child components directly in your template. Access them with get_component_child<T>().

HTML
<div>
    <spry-component name="HeaderComponent" sid="header"/>
    <spry-component name="TodoListComponent" sid="todo-list"/>
    <spry-component name="FooterComponent" sid="footer"/>
</div>
Vala
public override async void prepare() throws Error {
    var header = get_component_child<HeaderComponent>("header");
    header.title = "My App";
}

<spry-context> - Context Property Preservation

The <spry-context> tag marks a property to be preserved across action requests. When a component action is triggered, properties marked with this tag are encrypted and included in the action URL, then restored when the action is handled.

HTML
<!-- Mark properties to be preserved across actions -->
<spry-context property="demo_component_name"/>
<spry-context property="source_file"/>

<div sid="host">
    <span content-expr="this.demo_component_name">Demo</span>
    <button spry-action=":ShowSource" spry-target="host">Show Source</button>
</div>

How It Works

  • Properties marked with <spry-context property="name"/> are tracked
  • When an action is triggered, these properties are encrypted using CryptographyProvider
  • The encrypted context is included in the action URL
  • The server decrypts and restores the properties before calling handle_action()

⚠️ Security Warning: Replay Attacks

The encrypted context can be captured and replayed by malicious actors. While the data cannot be tampered with (it's cryptographically signed), an attacker could capture a valid request and replay it later.

  • Do not use context for sensitive data that could be exploited if replayed
  • Do not use context for authentication or authorization decisions
  • Consider adding timestamps or expiration for time-sensitive operations

Example attack: If context contains admin privileges, an attacker could capture a request from an admin session and replay it to gain unauthorized access.

Best Practices

  • Use context for non-sensitive data like IDs, flags, or UI configuration
  • Validate context data on the server before using it
  • Keep context data minimal - only include what's needed

spry-res - Static Resources

Use spry-res to reference Spry's built-in static resources. This resolves to /_spry/res/{resource-name} automatically.

HTML
<script spry-res="htmx.js"></script>
<script spry-res="htmx-sse.js"></script>

Next Steps