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.