Keyed lists
kj-for goes on a <template> with exactly one root element. It accepts item in items, (item, index) in items, objects ((value, key) in obj) and ranges (i in 5). Add :key so reordering moves DOM nodes — inputs keep their focus and typed text.
kj-if and kj-teleport
kj-if adds or removes its template's element entirely (unlike kj-show, which only hides it). kj-teleport="body" renders a template somewhere else — useful for modals that must escape an overflow: hidden parent — while keeping the component's state and event handlers.
Nothing left — nice.
Remove completed items?
View the source for this demo
<div kj-data="{ next: 4, draft: '', confirm: false,
todos: [{ id: 1, text: 'Read the docs', done: true }, { id: 2, text: 'Try a demo', done: false }, { id: 3, text: 'Ship it', done: false }] }">
<form class="row" @submit.prevent="if (draft.trim()) { todos.push({ id: next++, text: draft.trim(), done: false }); draft = '' }">
<div class="field" style="flex:1"><label for="lr-new">New item</label><input id="lr-new" kj-model="draft" placeholder="Something to do"></div>
<button style="align-self:end">Add</button>
<button type="button" style="align-self:end" @click="todos.reverse()">Reverse</button>
</form>
<ul class="lr-list">
<template kj-for="todo in todos" :key="todo.id">
<li :class="{ done: todo.done }">
<input type="checkbox" kj-model="todo.done" :aria-label="'Done: ' + todo.text">
<span kj-text="todo.text"></span>
<button type="button" @click="todos = todos.filter(t => t !== todo)" :aria-label="'Remove ' + todo.text">✕</button>
</li>
</template>
</ul>
<template kj-if="todos.length === 0"><p class="muted">Nothing left — nice.</p></template>
<p class="row" style="margin-top:12px"><span class="muted" kj-text="todos.filter(t => t.done).length + ' of ' + todos.length + ' done'"></span>
<button type="button" @click="confirm = true" :disabled="!todos.some(t => t.done)">Clear done…</button></p>
<template kj-teleport="body">
<div class="lr-modal" kj-show="confirm" @keydown.escape.window="confirm = false" role="dialog" aria-modal="true" aria-label="Clear completed items">
<div><p style="margin-top:0">Remove <b kj-text="todos.filter(t => t.done).length"></b> completed items?</p>
<div class="row"><button type="button" @click="todos = todos.filter(t => !t.done); confirm = false">Remove</button>
<button type="button" @click="confirm = false">Cancel</button></div></div>
</div>
</template>
</div>More examples
Index, objects and ranges
<template kj-for="(user, i) in users" :key="user.id"><li kj-text="(i + 1) + '. ' + user.name"></li></template>
<template kj-for="(value, key) in settings"><dt kj-text="key"></dt></template>
<template kj-for="n in 5"><span>★</span></template>API
Add/remove DOM; keyed lists over arrays, objects or ranges.
Reference: kj-if / kj-for →Two-way binding for inputs, checkboxes (booleans or arrays), radios and selects.
Reference: kj-model →Declare a component. Nested components see their parents.
Reference: kj-data →Migrating
x-for, x-if and x-teleport work as kj-for, kj-if and kj-teleport — and the x- spellings are accepted too.Related features
Conditional display
Show and hide parts of the page from state with kj-show.
Open →PulseTransitions
Fade, scale and slide elements in and out when kj-show or kj-if changes, using kj-transition modifiers or your own CSS classes.
Open →PulseReactive state
Declare state on an element with kj-data and every binding inside updates when it changes.
Open →