Interactivity

Native Svelte Syntax

Native Svelte markup is the clearest way to write larger interactive sections inside TemplateX .php views.

Write A Svelte-Style Component#

Use normal Svelte blocks, output, directives, and derived values:

phpresources/views/blocks/tabs.php
title: Tabscategory: Layout---<script>  let activeTab = 'profile';  const tabs = [    { id: 'profile', label: 'Profile', content: 'Profile content' },    { id: 'settings', label: 'Settings', content: 'Settings content' },  ];  const currentTab = $derived(tabs.find((tab) => tab.id === activeTab));</script><div class="tabs">  <div role="tablist" aria-label="Account sections">    {#each tabs as tab}      <button        type="button"        role="tab"        class:active={activeTab === tab.id}        aria-selected={activeTab === tab.id}        onclick={() => activeTab = tab.id}      >        {tab.label}      </button>    {/each}  </div>  <section role="tabpanel">    <h2>{currentTab.label}</h2>    <p>{currentTab.content}</p>  </section></div>

This is still a TemplateX .php view. Svelte owns {#each}, {value}, class:active={...}, onclick={() => ...}, $derived(...), and the browser updates.

State Promotion#

You can write $state(...) yourself, but you usually do not have to.

TemplateX promotes plain let variables to Svelte state when interactive markup binds them, prints them with Svelte syntax, uses them in a client-side condition, or updates them in an event expression.

Choose Braces By Ownership#

Use single braces for Svelte values:

php
<p>{count}</p>

Use double braces for TemplateX and WordPress values:

php
<h1>{{ title }}</h1>

Inside an island, double braces can also print Svelte values when the expression starts with a known Svelte name:

php
{#each tabs as tab}  <button>{{ tab.label }}</button>{/each}

That compiles like Svelte's native {tab.label}.

A double-brace tag that does not reference known Svelte state stays a server-rendered TemplateX value. Paired TemplateX tags, such as {{ cards }}...{{ /cards }} or {{ query:posts }}...{{ /query:posts }}, stay server-rendered even inside Svelte markup.

Mix In TemplateX Shorthands#

TemplateX event and binding shorthands also work inside normal Svelte blocks:

php
<script>  let apples = 0;  let enabled = false;  const sizes = ['small', 'large'];  function add(size) {    apples += size.length;  }</script><input type="number" bind="{{ apples }}"><input type="checkbox" bind:checked="{{ enabled }}">{#each sizes as size}  <button    type="button"    class:active={enabled}    onclick="{{ add(size) }}"  >    {size}: {apples}  </button>{/each}