Template Syntax

Courvux templates are plain HTML with directives and expression bindings. All expressions are full JavaScript (requires no strict CSP).

Interpolation

Use inside text nodes for reactive values:

HTML
<!-- Text interpolation <!-- Text interpolation -->
<p>{{ count }}</p>
<p>{{ price * qty }}</p>
<p>{{ active ? 'On' : 'Off' }}</p>
<p>{{ name.toUpperCase() }}</p>

Property & class & style bindings

Prefix any attribute with : to evaluate it as a JavaScript expression:

HTML
<!-- Property binding <!-- Property binding -->
<input :disabled="count > 10" />
<img :src="avatarUrl" :alt="user.name" />

<!-- Class binding (object | array | string) <!-- Class binding (object | array | string) -->
<div :class="{ active: isOn, 'text-muted': !isOn }"></div>
<div :class="['base', isOn ? 'on' : 'off']"></div>

<!-- Style binding <!-- Style binding -->
<span :style="{ color: textColor, fontSize: size + 'px' }"></span>
<span :style="'color:red; font-weight:bold'"></span>

Event binding

Use @event or cv:on:event. Access the raw event via $event.

HTML
<!-- Method reference <!-- Method reference -->
<button @click="increment">+1</button>

<!-- Inline expression <!-- Inline expression -->
<button @click="count++">+</button>
<button @click="count = 0">Reset</button>

<!-- $event — raw DOM event <!-- $event — raw DOM event -->
<input @input="search = $event.target.value" />

<!-- Modifiers <!-- Modifiers -->
<form @submit.prevent="onSubmit">...</form>
<button @click.stop="doThing">...</button>
<button @click.once="runOnce">...</button>

<!-- Key modifiers <!-- Key modifiers -->
<input @keydown.enter="submit" />
<input @keydown.esc="cancel" />

<!-- cv:on: prefix (alternative to @) <!-- cv:on: prefix (alternative to @) -->
<button cv:on:click="increment">+1</button>

cv-for — list rendering

Add :key for keyed reconciliation — Courvux reuses existing DOM nodes for matching keys.

HTML
<!-- Array <!-- Array -->
<li cv-for="item in items">{{ item }}</li>
<li cv-for="(item, index) in items">{{ index }}: {{ item }}</li>

<!-- Object <!-- Object -->
<li cv-for="(val, key) in person">{{ key }}: {{ val }}</li>

<!-- Keyed — recommended for dynamic lists <!-- Keyed — recommended for dynamic lists -->
<li cv-for="user in users" :key="user.id">
    {{ user.name }}
</li>

cv-if / cv-else-if / cv-else

Nodes are inserted and removed from the DOM.

HTML
<!-- Elements are added/removed from the DOM <!-- Elements are added/removed from the DOM -->
<p cv-if="count > 10">High</p>
<p cv-else-if="count > 0">Low</p>
<p cv-else>Zero</p>

cv-show

Toggles display: none. Node stays in the DOM.

HTML
<!-- Toggles display:none — stays in DOM <!-- Toggles display:none — stays in DOM -->
<div cv-show="isVisible">Panel content</div>

<!-- With Alpine-style transition <!-- With Alpine-style transition -->
<div cv-show="open" cv-transition>Fade in/out</div>
<div cv-show="open" cv-transition.scale>Scale + fade</div>

cv-model — two-way binding

HTML
<!-- Text input <!-- Text input -->
<input type="text" cv-model="name" />

<!-- Checkbox → boolean <!-- Checkbox → boolean -->
<input type="checkbox" cv-model="active" />

<!-- Select <!-- Select -->
<select cv-model="country">
    <option value="us">United States</option>
    <option value="mx">Mexico</option>
</select>

<!-- Modifiers <!-- Modifiers -->
<input cv-model.lazy="query" />     <!-- update on blur <!-- update on blur -->
<input cv-model.trim="username" />  <!-- strip whitespace <!-- strip whitespace -->
<input cv-model.number="price" />   <!-- coerce to number <!-- coerce to number -->
<input cv-model.debounce="search" />        <!-- 300ms debounce <!-- 300ms debounce -->
<input cv-model.debounce.500="search" />    <!-- custom delay <!-- custom delay -->

cv-html

Sets innerHTML. Sanitized by default — strips <script>, on*= handlers, and javascript: URLs so user-submitted content is safe to render. Add .raw to opt out when the markup is something you authored (Markdown rendered server-side, hand-curated copy).

HTML
<!-- Sanitized by default — strips <script>, on*= handlers, javascript: URLs.
     Safe for user-submitted content. , on*= handlers, javascript: URLs.
     Safe for user-submitted content. -->
<div cv-html="userContent"></div>

<!-- Opt out of sanitization with .raw — only for content YOU authored
     (Markdown rendered server-side, hand-curated HTML, etc.) <!-- Opt out of sanitization with .raw — only for content YOU authored
     (Markdown rendered server-side, hand-curated HTML, etc.) -->
<div cv-html.raw="myTrustedContent"></div>
Breaking change in 0.6.0: the default flipped from raw to sanitized. The pre-0.6 cv-html.sanitize still works (it's now a no-op). To restore the old raw behavior on a binding you control, switch cv-htmlcv-html.raw.

cv-data — inline scope

Self-contained reactive scope without component registration. Lighter than components — no lifecycle, no slots, no emits.

HTML
<!-- Inline reactive scope — no component registration needed <!-- Inline reactive scope — no component registration needed -->
<div cv-data="{ count: 0 }">
    <button @click="count--"></button>
    <span>{{ count }}</span>
    <button @click="count++">+</button>
</div>

<!-- With methods <!-- With methods -->
<div cv-data="{ open: false, toggle() { this.open = !this.open } }">
    <button @click="toggle()">{{ open ? 'Close' : 'Open' }}</button>
    <p cv-show="open">Content</p>
</div>

<!-- Nested scopes — child inherits parent keys <!-- Nested scopes — child inherits parent keys -->
<div cv-data="{ user: 'Alice' }">
    <div cv-data="{ role: 'admin' }">
        {{ user }} — {{ role }}
    </div>
</div>

Misc directives

HTML
<!-- cv-once — render once, skip future updates <!-- cv-once — render once, skip future updates -->
<strong cv-once>{{ initialValue }}</strong>

<!-- cv-ref — store element reference in $refs <!-- cv-ref — store element reference in $refs -->
<input cv-ref="myInput" />

<!-- cv-teleport — move to another DOM node <!-- cv-teleport — move to another DOM node -->
<div cv-show="modal" cv-teleport="body">...</div>

<!-- cv-cloak — hide until mounted <!-- cv-cloak — hide until mounted -->
<div id="app" cv-cloak></div>
← Quick Start Components →