Skip to main content
IndexedDB Signals

Signal backed by IndexedDB — survives page refresh, handles large data.

No data stored
<div id="demo-idb">
  <button id="btn-idb-set">Store Value</button>
  <button id="btn-idb-get">Read Value</button>
  <pre id="output-idb">No data stored</pre>
</div>
import { enhance } from 'weblisk';
import { idbSignal } from 'weblisk/state/idb.js';

enhance('#demo-idb', (el, { $ }) => {
  const [val, setVal, { ready }] = idbSignal('demo-idb-value', {
    message: 'none', ts: 0
  });

  ready.then(() => {
    $('#output-idb').textContent =
      `Current: ${JSON.stringify(val(), null, 2)}`;
  });

  $('#btn-idb-set').addEventListener('click', () => {
    const data = {
      message: `Stored at ${new Date().toLocaleTimeString()}`,
      ts: Date.now()
    };
    setVal(data);
  });

  $('#btn-idb-get').addEventListener('click', () => {
    $('#output-idb').textContent =
      `Current: ${JSON.stringify(val(), null, 2)}`;
  });
});
Cross-Tab Sync

Open this page in another tab — changes sync instantly via BroadcastChannel.

Counter: 0
import { effect, enhance } from 'weblisk';
import { synced } from 'weblisk/state/sync.js';

enhance('#demo-sync', (el, { $ }) => {
  const [count, setCount] = synced('demo-counter', 0);

  effect(() => {
    $('#output-sync').textContent =
      `Counter: ${count()}\n(Open another tab to see sync!)`;
  });

  $('#btn-sync').addEventListener('click',
    () => setCount(c => c + 1)
  );
});
Undo / Redo

Edit the text, then undo/redo with the buttons or keyboard shortcuts.

History: 1 entry
<input type="text" id="undo-input" placeholder="Type something..." />
<button id="btn-undo">↩ Undo</button>
<button id="btn-redo">↪ Redo</button>
<pre id="output-undo"></pre>
import { effect, enhance } from 'weblisk';
import { undoable } from 'weblisk/state/history.js';

enhance('#demo-undo', (el, { $ }) => {
  const [text, setText, { undo, redo, canUndo, canRedo }] =
    undoable('');

  let debounce;
  $('#undo-input').addEventListener('input', () => {
    clearTimeout(debounce);
    debounce = setTimeout(
      () => setText($('#undo-input').value), 300
    );
  });

  effect(() => {
    const val = text();
    if ($('#undo-input').value !== val)
      $('#undo-input').value = val;
    $('#output-undo').textContent =
      `Value: "${val}"\nCan undo: ${canUndo()} | Can redo: ${canRedo()}`;
  });

  $('#btn-undo').addEventListener('click', undo);
  $('#btn-redo').addEventListener('click', redo);
});
Form State Machine

Declarative form with built-in validation, submit state, and error tracking.

Fill out the form above
import { effect, enhance } from 'weblisk';
import { formState } from 'weblisk/state/form.js';

enhance('#demo-form', (el, { $ }) => {
  const fs = formState({
    fields: {
      email: {
        required: true,
        pattern: /^[^\s@]+@[^\s@]+$/,
        message: 'Invalid email'
      },
      age: {
        required: true,
        validate: v => {
          const n = parseInt(v);
          if (isNaN(n) || n < 1 || n > 120) return 'Must be 1-120';
          return null;
        }
      }
    },
    onSubmit: async (values) => {
      await new Promise(r => setTimeout(r, 500));
      console.log('Submitted:', values);
    }
  });

  fs.bind($('#demo-form-el'));

  effect(() => {
    const errs = fs.errors();
    const vals = fs.values();
    let text = `Values: ${JSON.stringify(vals)}\n`;
    text += `Valid: ${fs.valid()} | Dirty: ${fs.dirty()}\n`;
    if (Object.keys(errs).length)
      text += `Errors: ${JSON.stringify(errs)}`;
    if (fs.submitted()) text += '\nForm submitted!';
    if (fs.submitting()) text = 'Submitting...';
    $('#output-form').textContent = text;
  });
});