Your first app
A full todo list in one file. This is idiomatic Mikata - the component function runs once, signals drive updates, and each()/show() handle reactive lists and conditional branches without re-running the surrounding component.
import { signal, computed, render, show, each } from 'mikata';
function TodoList() {
const [todos, setTodos] = signal<{ id: number; text: string; done: boolean }[]>([]);
const [input, setInput] = signal('');
const remaining = computed(() => todos().filter((t) => !t.done).length);
const add = () => {
if (!input().trim()) return;
setTodos([...todos(), { id: Date.now(), text: input(), done: false }]);
setInput('');
};
const toggle = (id: number) =>
setTodos(todos().map((t) => (t.id === id ? { ...t, done: !t.done } : t)));
return (
<div>
<input
value={input()}
onInput={(e) => setInput(e.currentTarget.value)}
onKeydown={(e) => e.key === 'Enter' && add()}
/>
<button onClick={add}>Add</button>
<ul>
{each(todos, (todo) => (
<li
style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
onClick={() => toggle(todo.id)}
>
{todo.text}
</li>
))}
</ul>
{show(
() => todos().length > 0,
() => <p>{remaining()} remaining</p>
)}
</div>
);
}
render(() => <TodoList />, document.getElementById('app')!);What's happening
signalreturns[getter, setter]. The getter is a function - call it to read, and Mikata tracks the read in the surrounding reactive context.computedis a derived signal. It re-runs only when a dependency it read changes.eachrenders a keyed reactive list without re-creating rows.showdoes the same for a single branch.- Event handlers receive native events, typed against the element they're bound to -
e.currentTarget.valueis typed for you.