Pulse · Alpine-style

Conditional display

kj-show="step === 2" shows an element only while the expression is true. Build wizards, tabs and empty states from one piece of state.

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.

Live · Three-step signup wizard

  1. Account
  2. Plan
  3. Confirm

What should we call you?

Pick a plan

Plan

Confirm

View the source for this demo
HTML
<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>
JavaScript
$.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

HTML
<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

kj-show="expr"

Show/hide via display based on a truthy expression.

Reference: kj-show →

Migrating

Coming from Alpine.jsx-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