kj-show toggles display; the element stays in the DOM, keeping any input the user typed. Because the whole wizard is driven by a single step value, there is no chance of two steps showing at once.
Accessibility matters most at the moment content changes. The go() method below moves keyboard focus to the new step's heading, so screen-reader and keyboard users land where the new content starts.
- Account
- Plan
- Confirm
What should we call you?
Pick a plan
Confirm
View the source for this demo
<div kj-data="wizard" class="wz">
<ol class="wz-steps" aria-label="Progress">
<li :aria-current="step === 1 ? 'step' : false">Account</li>
<li :aria-current="step === 2 ? 'step' : false">Plan</li>
<li :aria-current="step === 3 ? 'step' : false">Confirm</li>
</ol>
<section kj-show="step === 1">
<h4 id="wz-h1" tabindex="-1">What should we call you?</h4>
<div class="field"><label for="wz-name">Name</label><input id="wz-name" type="text" kj-model="name" autocomplete="name"></div>
<div class="row"><button type="button" @click="go(2)" :disabled="!name.trim()">Next</button></div>
</section>
<section kj-show="step === 2">
<h4 id="wz-h2" tabindex="-1">Pick a plan</h4>
<fieldset style="border:0;padding:0;display:grid;gap:8px"><legend class="sr-only">Plan</legend>
<label><input type="radio" name="wz-plan" value="free" kj-model="plan"> Free — one site</label>
<label><input type="radio" name="wz-plan" value="pro" kj-model="plan"> Pro — unlimited sites</label>
<label><input type="radio" name="wz-plan" value="team" kj-model="plan"> Team — shared stores</label>
</fieldset>
<label><input type="checkbox" kj-model="emails"> Send me release notes</label>
<div class="row"><button type="button" @click="go(1)">Back</button><button type="button" @click="go(3)">Next</button></div>
</section>
<section kj-show="step === 3">
<h4 id="wz-h3" tabindex="-1">Confirm</h4>
<p kj-text="'Name: ' + name + ' · Plan: ' + plan + ' · Release notes: ' + (emails ? 'yes' : 'no')"></p>
<div class="row"><button type="button" @click="go(2)">Back</button><button type="button" @click="go(4)">Create account</button></div>
</section>
<section kj-show="step === 4">
<h4 id="wz-h4" tabindex="-1" kj-text="'Welcome aboard, ' + name + ' ✓'"></h4>
<div class="row"><button type="button" @click="name = ''; go(1)">Start over</button></div>
</section>
</div>$.component('wizard', () => ({
step: 1, name: '', plan: 'pro', emails: true,
go(n) {
this.step = n;
setTimeout(() => $('#wz-h' + n).focus(), 0); // move focus to the new step for keyboard & screen-reader users
},
}));More examples
Empty states
<div kj-data="{ items: [] }">
<p kj-show="!items.length">Nothing here yet — add your first item.</p>
<ul kj-show="items.length" kj-html="…"></ul>
</div>API
Migrating
x-show → kj-show. x-if (adding/removing from the DOM) and x-transition are not included; combine kj-show with Motion for animated reveals.Related features
Reactive state
Declare state on an element with kj-data and every binding inside updates when it changes.
Open →PulseEvent directives
Handle events right in your markup with @click and kj-on:, including the .prevent, .stop, .enter and .escape modifiers.
Open →PulseTwo-way binding
Bind form controls to state in both directions with kj-model — text, number, range, checkbox, radio and select.
Open →