Pulse · Alpine-style

Text & HTML binding

kj-text writes text — it can never inject markup. kj-html writes HTML for the cases where you built the markup yourself.

Any JavaScript expression works inside the attribute: arithmetic, ternaries, method calls, template-free string concatenation. The binding re-runs whenever the component's state changes.

The safe pattern for rich previews is escape first, then decorate: run user text through $.escape(), then add your own tags for hashtags or mentions. That way the only HTML that reaches the page is HTML you wrote.

Live · Post composer

Preview (kj-html)

View the source for this demo
HTML
<div kj-data="composer">
  <label for="cp-msg">Your post</label>
  <textarea id="cp-msg" rows="3" kj-model="msg" maxlength="400" style="margin-top:6px"></textarea>
  <p class="row" style="justify-content:space-between;margin:8px 0 16px">
    <span class="muted" kj-text="words() + ' words'"></span>
    <span :class="left() < 0 ? 'no' : left() < 20 ? 'partial' : 'muted'" kj-text="left() + ' characters left'" aria-live="polite"></span>
  </p>
  <h4 style="margin:0 0 8px">Preview <small class="muted">(kj-html)</small></h4>
  <div class="cp-preview" kj-html="preview()"></div>
</div>
JavaScript
$.component('composer', () => ({
  msg: 'Shipping #jskjs today — one script, four superpowers. Thanks @kchecker! <b>This tag stays text.</b>',
  left()  { return 140 - this.msg.length; },
  words() { return this.msg.trim() ? this.msg.trim().split(/\s+/).length : 0; },
  preview() {
    return $.escape(this.msg)                       // 1. escape everything the user typed
      .replace(/#(\w+)/g, '<mark>#$1</mark>')       // 2. then add markup we control
      .replace(/@(\w+)/g, '<a href="https://github.com/$1" rel="noopener">@$1</a>');
  },
}));

More examples

Common bindings

HTML
<span kj-text="count"></span>
<p kj-text="user ? 'Hi ' + user.name : 'Please sign in'"></p>
<ul kj-html="items.map(i => '<li>' + $.escape(i) + '</li>').join('')"></ul>

API

kj-text="expr" · kj-html="expr"

Bind textContent or innerHTML to an expression. Prefer kj-text for anything user-supplied.

Reference: kj-text / kj-html →

Migrating

Coming from Alpine.jsx-text → kj-text, x-html → kj-html.

Related features