Directives reference
A complete tour of the directives you can use in ichigo.js templates, with their modifiers and examples. The syntax is Vue-like, so Vue users will feel at home.
Directives are written as element attributes. In addition to the v- forms, the two most common have shorthands:
| Full | Shorthand | Meaning |
|---|---|---|
v-bind:href="url" |
:href="url" |
attribute binding |
v-on:click="fn" |
@click="fn" |
event listener |
Live, runnable demos are linked from the Examples page.
Text interpolation {{ }}
Embed an expression with double curly braces. The result is inserted as text and HTML is escaped.
<p>Hello, {{ user.name }} ({{ items.length }} items)</p>
<p>Total: {{ (price * quantity).toLocaleString() }}</p>
Interpolations accept JavaScript expressions (not statements) and can reference data / computed / methods as well as helpers like $refs.
v-text / v-html
v-text replaces the element's textContent with the expression result. Unlike {{ }}, it overwrites the whole content, which is handy to avoid a flash of un-interpolated text.
<span v-text="message"></span>
<!-- Same result as {{ message }} (HTML is escaped) -->
v-html sets innerHTML, rendering raw HTML.
<div v-html="htmlContent"></div>
⚠️ Security:
v-htmlis an easy path to XSS. Use it only on trusted content and never on user input. For plain text usev-textor{{ }}.
v-bind (:)
Bind attributes and properties to an expression.
<img :src="imageUrl" :alt="imageAlt">
<a :href="`/users/${user.id}`">Profile</a>
<button :disabled="isLoading">Submit</button>
Binding class
Object syntax (apply a class when its value is truthy) and array syntax are both supported, and merge with any static class.
<!-- Object syntax -->
<div class="card" :class="{ active: isActive, 'is-done': todo.completed }"></div>
<!-- Array syntax -->
<div :class="['badge', isError ? 'badge-error' : 'badge-ok']"></div>
Binding style
<div :style="{ height: value * 2 + 'px', color: theme.ink }"></div>
v-if / v-else-if / v-else
Conditional rendering. A false branch is not present in the DOM (it is created and destroyed).
<div v-if="count > 10">Greater than 10</div>
<div v-else-if="count > 5">Greater than 5</div>
<div v-else>5 or less</div>
v-else-if / v-else must immediately follow the matching v-if element.
v-show
v-show always renders the element and only toggles display. For elements you toggle frequently, v-show is cheaper than v-if (which creates/destroys).
<div v-show="isVisible">Can be toggled</div>
v-for
Render lists from arrays, objects, numbers, or strings. Add :key for stable diffing (recommended).
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }}: {{ item.name }}
</li>
</ul>
Wrapper-free repetition with <template>
To repeat multiple nodes without an extra wrapper element, place v-for on a <template>.
<dl>
<template v-for="row in rows" :key="row.id">
<dt>{{ row.term }}</dt>
<dd>{{ row.description }}</dd>
</template>
</dl>
A <template> supports either v-for or v-if, but not both on the same element. When you need both, nest them.
<template v-for="item in items" :key="item.id">
<div v-if="item.visible">{{ item.name }}</div>
</template>
Combining v-for and v-if
On a regular (non-<template>) element you can combine v-for and v-if. v-for is evaluated first, then v-if per iteration.
<li v-for="item in items" v-if="item.visible" :key="item.id">{{ item.name }}</li>
v-on (@)
Listen for events. Handlers can be a method name or an inline expression.
<button @click="handleClick">Click</button>
<button @click="count++">Inline expression</button>
<form @submit.prevent="handleSubmit">Submit</form>
Handler context
Every event handler receives the event as the first argument and $ctx as the second.
methods: {
handleClick(event, $ctx) {
// event … the DOM event
// $ctx.element … the DOM element
// $ctx.vnode … the VNode instance
// $ctx.userData … Proxy-free storage
}
}
Event modifiers
| Modifier | Effect |
|---|---|
.stop |
stopPropagation() |
.prevent |
preventDefault() |
.capture |
listen in the capture phase |
.self |
only when the event's target is the element itself |
.once |
run at most once |
<div @click.stop="onClick">Stop propagation</div>
<a @click.prevent="onNav">Prevent default</a>
Key modifiers (KeyboardEvent)
.enter .tab .delete (Delete / Backspace) .esc (.escape) .space .up .down .left .right
<input @keyup.enter="submit" @keyup.esc="cancel">
Mouse button modifiers (MouseEvent)
.left .middle .right
<a @mousedown.middle="openInNewTab">Middle-click</a>
<div @contextmenu.prevent="onMenu">Suppress the context menu</div>
System modifier keys
.shift .ctrl .alt .meta. Add .exact to require that no other modifier keys are held.
<button @click.shift="onShiftClick">Shift+Click</button>
<button @click.ctrl.exact="onCtrlOnly">Ctrl-only click</button>
v-model
Two-way binding for form elements.
<input v-model="message">
<textarea v-model="bio"></textarea>
Modifiers
| Modifier | Effect |
|---|---|
.lazy |
sync on change instead of input |
.number |
cast to a number |
.trim |
trim surrounding whitespace |
<input v-model.trim="username">
<input v-model.number="age" type="number">
Per-element behavior
<!-- Checkbox (boolean) -->
<input type="checkbox" v-model="isChecked">
<!-- Custom true/false values -->
<input type="checkbox" v-model="status" :true-value="'yes'" :false-value="'no'">
<!-- Bound to an array (multi-select) -->
<input type="checkbox" value="a" v-model="selected">
<input type="checkbox" value="b" v-model="selected">
<!-- Radio -->
<input type="radio" value="a" v-model="picked">
<input type="radio" value="b" v-model="picked">
<!-- Select (re-applies the selection even when options are generated with v-for) -->
<select v-model="choice">
<option v-for="o in options" :key="o.value" :value="o.value">{{ o.label }}</option>
</select>
- Checkbox — binds to a boolean, to a
:true-value/:false-valuepair, or to an array (thevalueis added/removed) - Radio — binds to the selected
value(:value) - Select — binds to the selected option's value
A writable computed ({ get, set }) can also be a v-model target — see the Core guide.
v-focus
Manage focus declaratively. Focus is deferred via requestAnimationFrame, so elements revealed just before the directive runs (e.g. inside a v-if) still receive focus reliably.
<!-- Focus once after mount -->
<input v-focus>
<!-- Focus + select all -->
<input v-focus.select>
<!-- Focus + caret at the end -->
<input v-focus.cursor-end value="prefilled">
<!-- Conditional: fires on the falsy → truthy edge -->
<input v-focus="isEditing">
- Without an expression — focused exactly once after mount
- With an expression — focuses only on the falsy → truthy edge (never re-focuses on every update)
- Modifiers
.select(select all) /.cursor-end(caret at the end)
Lifecycle hooks
Run code at each stage of an element's lifecycle. Subscribe with @ (v-on); each hook receives $ctx (element / vnode / userData). Use them to integrate third-party libraries or drive animations.
<div v-if="show"
@mount="onMount"
@mounted="onMounted"
@update="onUpdate"
@updated="onUpdated"
@unmount="onUnmount"
@unmounted="onUnmounted">
Content
</div>
| Hook | When |
|---|---|
@mount |
before mounting to the DOM |
@mounted |
after mounting |
@update |
before an update |
@updated |
after an update |
@unmount |
before teardown begins (element still in the DOM) |
@unmounted |
after teardown ($ctx.element still available) |
userData and automatic cleanup
$ctx.userData is a Map storage that is not touched by the reactive proxy — ideal for holding third-party instances (Chart.js, etc.). Objects with a close() method are cleaned up automatically on teardown.
methods: {
onMounted($ctx) {
const chart = new Chart($ctx.element.querySelector('canvas'), { /* ... */ });
$ctx.userData.set('chart', chart);
},
onUpdated($ctx) {
$ctx.userData.get('chart')?.update();
},
onUnmount($ctx) {
$ctx.userData.get('chart')?.destroy();
}
}
The teardown order is:
@unmountfires (element still in the DOM)userDataauto-cleanup (close()called)- child nodes destroyed recursively
- dependencies unregistered
- directive manager cleanup
@unmountedfires (removed from the DOM, but$ctx.elementstill available)
Hooks also fire when toggled by v-if and for each v-for item. See the live Lifecycle Hooks demo.
Observer directives
Use the browser's native Observer APIs declaratively. Each one is automatically disconnected on teardown, and handlers receive $ctx. Pass options with :options.<kind> or the generic :options.
v-resize
Monitor element size changes with ResizeObserver.
<div v-resize="onResize" :options.resize="{ box: 'border-box' }">
{{ width }}px × {{ height }}px
</div>
methods: {
onResize(entries, $ctx) {
const r = entries[0].contentRect;
this.width = Math.round(r.width);
this.height = Math.round(r.height);
}
}
→ Live demo: Resize Observer
v-intersection
Detect visibility with IntersectionObserver — perfect for lazy loading, infinite scroll, and scroll-triggered effects.
<div v-intersection="onIntersection" :options.intersection="{ threshold: 0.5 }">
{{ isVisible ? 'Visible' : 'Not visible' }}
</div>
methods: {
onIntersection(entries, $ctx) {
this.isVisible = entries[0].isIntersecting;
}
}
→ Live demo: Intersection Observer
v-performance
Observe performance entries with PerformanceObserver. The handler signature is (entries, observer, options, $ctx).
<div v-performance="onPerformance" :options.performance="{ entryTypes: ['measure', 'mark'] }">
Monitored
</div>
methods: {
onPerformance(entries, observer, options, $ctx) {
entries.getEntries().forEach(e => console.log(`${e.name}: ${e.duration}ms`));
}
}
→ Live demo: Performance Observer
Next steps
- Reactivity and components → Core guide and Components
- Real combinations → Examples