CHART/EXAMPLES/LIVE ISLANDS

Small, compiled, interactive

Every demo below is a marisjs client island compiled to direct DOM calls - no framework code runs on this page. The source shown is the actual component source.

chart 01 · source & live

Four islands, one primitive

Each demo uses signal() and a few hand-written handlers. Every source below runs live beside its code - same component, hydrated as an island.

01

Signal - a counter

Two handlers, one signal. No hook rules, no stale closures.

// @runsOn client
import { signal } from '@marisjs/runtime';

type Props = {};

export function CounterDemo(props: Props) {
  const count = signal(0);
  function inc() { count.set(count.value + 1); }
  function dec() { count.set(count.value - 1); }
  function reset() { count.set(0); }
  return (
    <div class="counter-wrap">
      <div class="counter-num">{count.value}</div>
      <div class="counter-btns">
        <button class="counter-btn" onClick={dec} disabled={count.value === 0} aria-label="decrement">-</button>
        <button class="counter-btn" onClick={inc} aria-label="increment">+</button>
        <button class="counter-reset" onClick={reset}>reset</button>
      </div>
    </div>
  );
}
01 Signal useState, minus the hook
02

Computed + For - reactive bars

A computed max drives bar widths; each slider is a signal write.

// @runsOn client
import { signal, computed } from '@marisjs/runtime';

type Props = {};

export function BarsDemo(props: Props) {
  const q1 = signal(35);
  const q2 = signal(70);
  const q3 = signal(45);
  const maxVal = computed(() => Math.max(q1.value, q2.value, q3.value, 1));
  const total = computed(() => q1.value + q2.value + q3.value);
  const rows = [
    { id: 'q1', label: 'Q1', sig: q1 },
    { id: 'q2', label: 'Q2', sig: q2 },
    { id: 'q3', label: 'Q3', sig: q3 },
  ];

  function set1(e: any) { q1.set(Number(e.target.value)); }
  function set2(e: any) { q2.set(Number(e.target.value)); }
  function set3(e: any) { q3.set(Number(e.target.value)); }

  return (
    <div class="play-body">
      <div>
        <div class="slider-row">
          <span class="slider-label">Q1</span>
          <input type="range" min="0" max="100" value={q1.value} onInput={set1}></input>
          <span class="slider-val">{q1.value}</span>
        </div>
        <div class="slider-row">
          <span class="slider-label">Q2</span>
          <input type="range" min="0" max="100" value={q2.value} onInput={set2}></input>
          <span class="slider-val">{q2.value}</span>
        </div>
        <div class="slider-row">
          <span class="slider-label">Q3</span>
          <input type="range" min="0" max="100" value={q3.value} onInput={set3}></input>
          <span class="slider-val">{q3.value}</span>
        </div>
      </div>
      <div class="bars">
        <For each={rows} key={(row) => row.id}>
          {(row) => (
            <div class="bar-track">
              <div class="bar-fill" style={'width:' + Math.round((row.sig.value / maxVal.value) * 100) + '%'}>
                {row.label} {row.sig.value}
              </div>
            </div>
          )}
        </For>
      </div>
      <div class="play-totals">
        <div class="total-chip">
          <div class="t-label">quarter total</div>
          <div class="t-value"><em>{total.value}</em></div>
        </div>
      </div>
    </div>
  );
}
02 Computed + For derived bars, reactive styles
03

List - a todo app

Per-item signals keep rows independently reactive inside .

// @runsOn client
import { signal, computed } from '@marisjs/runtime';

type Props = {};

export function TodoDemo(props: Props) {
  const items = signal([
    { id: 1, text: 'Install marisjs', done: signal(true) },
    { id: 2, text: 'Create pages/Index.tsx', done: signal(false) },
    { id: 3, text: 'Add a signal()', done: signal(false) },
  ]);
  const draft = signal('');
  const remaining = computed(() => items.value.filter((i) => !i.done.value).length);
  const nextId = computed(() => items.value.reduce((m, i) => Math.max(m, i.id), 0) + 1);

  function addItem() {
    if (draft.value.trim() === '') return;
    items.set([...items.value, { id: nextId.value, text: draft.value, done: signal(false) }]);
    draft.set('');
  }
  function toggle(id: number) {
    const item = items.value.find((i) => i.id === id);
    if (item) item.done.set(!item.done.value);
  }
  function remove(id: number) {
    items.set(items.value.filter((i) => i.id !== id));
  }
  function clearDone() {
    items.set(items.value.filter((i) => !i.done.value));
  }
  function handleDraft(e: any) { draft.set(e.target.value); }
  function handleKey(e: any) {
    if (e.key === 'Enter') addItem();
  }

  return (
    <div class="todo-wrap">
      <div class="todo-input-row">
        <input type="text" placeholder="Add a task..." value={draft.value} onInput={handleDraft} onKeyDown={handleKey}></input>
        <button class="todo-add-btn" onClick={addItem}>Add</button>
      </div>
      <ul class="todo-list">
        <For each={items.value} key={(item) => item.id}>
          {(item) => (
            <li class={'todo-item' + (item.done.value ? ' done' : '')}>
              <button class="todo-toggle" onClick={() => toggle(item.id)} aria-label="toggle">{item.done.value ? '✓' : ''}</button>
              <span class="todo-text">{item.text}</span>
              <button class="todo-del" onClick={() => remove(item.id)} aria-label="delete">x</button>
            </li>
          )}
        </For>
      </ul>
      <div class="todo-foot">
        <span>{remaining.value} remaining</span>
        <button class="todo-clear" onClick={clearDone}>clear done</button>
      </div>
    </div>
  );
}
03 List For + per-item signals
04

Binding - an input form

An input writes a signal; computed values derive everything else.

// @runsOn client
import { signal, computed } from '@marisjs/runtime';

type Props = {};

export function FormDemo(props: Props) {
  const name = signal('maris');
  const shout = computed(() => name.value.toUpperCase());
  const length = computed(() => name.value.length);
  function handleName(e: any) { name.set(e.target.value); }
  function reset() { name.set('maris'); }
  return (
    <div class="form-wrap">
      <div class="form-field">
        <label for="demo-name">your name</label>
        <input id="demo-name" type="text" value={name.value} onInput={handleName} placeholder="Type your name"></input>
      </div>
      <p class="form-greeting">Hello, <strong>{shout.value}</strong></p>
      <div class="form-meta">
        <span class="form-chip">length: {length.value}</span>
        <span class={'form-chip' + (name.value.length > 12 ? ' warn' : ' ok')}>{name.value.length > 12 ? 'long name' : 'looks good'}</span>
        <button class="todo-clear" onClick={reset}>reset</button>
      </div>
    </div>
  );
}
04 Binding input to computed, top-down

Want to tinker? The playground pairs each of these sources with its live island, one tab at a time.