Gyral 0.3.10
Model-View-Intent web components on the modern web platform.
Your app is a pure function, and side effects happen at the edges, as data. Gyral builds that loop on custom elements, semantic HTML, modern CSS and a small view layer of its own, with no runtime dependencies.
pnpm add @gyral/coreOne loop, three pure parts
Events become messages, messages become state, state becomes HTML. That's the whole app.
import { define, html } from '@gyral/core';
type Msg = { _tag: 'Increment' } | { _tag: 'Decrement' };
export const Counter = define<{ count: number }, Msg>()('my-counter', {
init: () => ({ count: 0 }),
// Intent: parse platform events into typed messages.
intent: {
Increment: () => ({ _tag: 'Increment' }),
Decrement: () => ({ _tag: 'Decrement' }),
},
// Model: one pure reducer per message, exhaustive by type.
update: {
Increment: (s) => ({ count: s.count + 1 }),
Decrement: (s) => ({ count: s.count - 1 }),
},
// View: a pure template that names intents. No closures.
view: (s, i) => html`
<button type="button" data-intent=${i.Decrement}>−</button>
<output>${s.count}</output>
<button type="button" data-intent=${i.Increment}>+</button>
`,
});- Intent
- waiting for a click
- Model
- { count: 0 }
- View
- <output>0</output>
Built on the platform, not around it
Standard web components
Every Gyral component is a custom element. Use it in any page, any framework, or none, next to any other custom element.
Typed, exhaustive updates
Messages are tagged unions. Forget a case and TypeScript tells you, before a user does.
Server rendering that hydrates in place
Pages render on the server with Declarative Shadow DOM and wake up in the browser without re-rendering, under a strict Content Security Policy.
Forms on the platform
Real <form> elements, native validation, Standard Schema parsing, and forms that work before JavaScript loads.
Effects are data
Update returns commands; drivers run them. HTTP, routing, time and storage stay at the edges, and tests need no mocks.
Templates checked before they run
A template compiler and an ESLint plugin catch event bindings, broken markup and stale list rows at build time and in your editor, with the fix in the message.
Pure means testable
Update and view are plain functions, so you test behaviour by calling them. When you need the browser, @gyral/testing mounts real components in Chromium, renders them on the server and checks they hydrate, and runs timers on a virtual clock.
import { run, step } from '@gyral/testing';
import { Counter } from './counter.js';
// No DOM, no mocks: the model is a function.
const inc = { _tag: 'Increment' } as const;
step(Counter.spec, { count: 1 }, inc).state;
// → { count: 2 }
run(Counter.spec, [inc, inc, inc]).state;
// → { count: 3 }One store, four looks
gyral-shop is a full department store built with Gyral: catalog, search, accounts, cart, checkout, orders and admin. Its themes change only CSS. The markup stays the same, the way the CSS Zen Garden proved it could. Try the live demo.

Gyral Goods, the house style 
Marketplace, dense and fast 
Supercenter, bold and friendly 
Boutique, quiet and editorial
Inspired by Cycle.js
Cycle.js showed that a web app can be one visible loop with every effect at the edge. Gyral is a new framework that carries that idea forward. It replaces streams with plain functions and the virtual DOM with the platform's own components, and credits the original in its license notice.
Build your first component
Install one package and write one function. The guide takes about ten minutes.
Read the guide