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.
<div sid="container">
<span sid="title">Hello</span>
<button sid="submit-btn">Submit</button>
</div>
// 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.
<!-- 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.
<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.
<!-- In HeaderComponent -->
<div id="header" spry-global="header">
<h1>Todo App</h1>
<span sid="count">0 items</span>
</div>
// 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.
<!-- 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.
<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.
<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.
<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>().
<div>
<spry-component name="HeaderComponent" sid="header"/>
<spry-component name="TodoListComponent" sid="todo-list"/>
<spry-component name="FooterComponent" sid="footer"/>
</div>
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.
<!-- 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.
<script spry-res="htmx.js"></script>
<script spry-res="htmx-sse.js"></script>