Directives
Full reference for all template directives supported by htmlc.
v-if / v-else-if / v-else
Renders the element only when the expression is truthy. Whitespace-only text nodes between branches are ignored.
<p v-if="role === 'admin'">Admin panel</p>
<p v-else-if="role === 'editor'">Editor view</p>
<p v-else>Read-only view</p>
Works on <template> elements too (renders children only, no wrapper element):
<template v-if="items.length > 0">
<ul>
<li v-for="item in items">{{ item }}</li>
</ul>
</template>
<template v-else>
<p>No items.</p>
</template>
v-show
Adds style="display:none" when the expression is falsy. The element is always rendered (unlike v-if). Merges with any existing style attribute.
<div v-show="isVisible">Visible when isVisible is truthy</div>
v-switch / v-case / v-default
Switch/case conditional (implements Vue RFC #482). Must be on a <template> element. Renders the first matching v-case branch.
<template v-switch="status">
<div v-case="'active'">Active</div>
<div v-case="'pending'">Pending approval</div>
<div v-default>Unknown status</div>
</template>
v-for
Repeats the element for each item in the iterable. Supports arrays, maps, and objects.
<!-- Array -->
<li v-for="item in items">{{ item }}</li>
<!-- With index -->
<li v-for="(item, index) in items">{{ index }}: {{ item }}</li>
<!-- Object/map -->
<li v-for="(value, key) in obj">{{ key }}: {{ value }}</li>
<!-- Range (integer) -->
<li v-for="i in 5">{{ i }}</li>
Note: Map iteration order follows Go's reflect.MapKeys() — not insertion order. Sort your maps before passing them if order matters.
v-bind / :attr
Dynamically binds an HTML attribute to an expression. The shorthand is :.
<!-- Long form -->
<a v-bind:href="url">Link</a>
<!-- Shorthand -->
<a :href="url">Link</a>
<img :src="imageUrl" :alt="imageAlt" />
<!-- Boolean attributes: rendered only when truthy -->
<button :disabled="isLoading">Submit</button>
<!-- Class binding -->
<div :class="isActive ? 'active' : ''">...</div>
When passing props to a component, :propName evaluates the expression:
<Card :title="post.title" :author="post.author" />
:class — object and array syntax
<!-- Object: keys with truthy values are included -->
<div :class="{ active: isActive, disabled: !isEnabled }">...</div>
<!-- Array: non-empty string elements are included -->
<div :class="['btn', isPrimary ? 'primary' : '']">...</div>
<!-- Static class and :class are merged -->
<div class="card" :class="{ featured: post.featured }">...</div>
:style — object syntax
<!-- camelCase keys are converted to kebab-case in output -->
<p :style="{ fontSize: '14px', backgroundColor: theme.bg }">...</p>
Boolean attributes
When a bound attribute name is a recognised boolean attribute
(disabled, checked, selected,
readonly, required, multiple,
autofocus, open), it is omitted
entirely when the value is falsy, and rendered without a value
when truthy.
<button :disabled="isLoading">Submit</button>
<!-- renders as <button> when isLoading is false -->
<!-- renders as <button disabled> when isLoading is true -->
v-bind="obj" — attribute spreading
When v-bind is used without an attribute name its value must
evaluate to a map[string]any. Each entry is spread as an HTML
attribute. class and style keys follow the same
merge rules. Boolean attribute semantics apply per key.
<!-- Spread HTMX attributes -->
<button v-bind="htmxAttrs">Delete</button>
<!-- Spread props into a child component -->
<Card v-bind="cardProps" :title="override" />
On child components, explicit :prop bindings take precedence
over keys in the spread map.
v-html
Sets the element's inner HTML to the expression value. The value is not HTML-escaped. Only use with trusted content.
<div v-html="renderedMarkdown"></div>
Warning: Never use v-html with user-supplied data — it can introduce XSS vulnerabilities.
v-text
Sets the element's text content to the expression value. HTML-escaped. Replaces all child nodes.
<span v-text="message"></span>
<!-- equivalent to -->
<span>{{ message }}</span>
v-pre
Skips all interpolation and directive processing for the element and all
its descendants. Mustache syntax ({{ }}) is emitted literally.
The v-pre attribute itself is stripped from the output.
<!-- This renders literally: {{ raw }} -->
<code v-pre>{{ raw }}</code>
Use v-pre to show template syntax as documentation or source
examples without the engine treating it as an expression.
v-slot / #slot
Passes content into a named slot of a child component.
<!-- In Layout.vue -->
<header><slot name="header" /></header>
<main><slot /></main>
<!-- Usage -->
<Layout>
<template #header>
<nav><a href="/">Home</a></nav>
</template>
<p>Page content goes into the default slot.</p>
</Layout>
Scoped slots (slot props):
<!-- In List.vue -->
<ul>
<li v-for="item in items">
<slot :item="item">{{ item }}</slot>
</li>
</ul>
<!-- Usage -->
<List :items="posts">
<template #default="{ item }">
<a :href="item.url">{{ item.title }}</a>
</template>
</List>
<component :is>
Renders a component whose name is determined at runtime. The :is
expression must evaluate to a non-empty string naming a registered component
or a standard HTML element.
<!-- Resolve from a variable -->
<component :is="activeView" />
<!-- Inline string literal -->
<component :is="'Card'" :title="pageTitle">
<p>slot content</p>
</component>
<!-- Switch between components in a loop -->
<div v-for="item in items">
<component :is="item.type" :data="item" />
</div>
- All attributes other than
:isare forwarded as props. - Default and named slots work exactly as with static component tags.
-
If the resolved name is a standard HTML element (e.g.
"div"), it is rendered as a plain tag, not looked up in the component registry. :isis required; omitting it is a render error.
Stripped directives
These directives are parsed but produce no output — they are client-side only and have no meaning in a server-side renderer:
v-model— two-way binding@event/v-on:event— event listenersv-once— one-time render optimisation hintv-memo— memoisation hintv-cloak— FOUC prevention