A reactive framework
for user interfaces.
Retend gives you JSX, reactive state, and the tools to build complete applications for the web, desktop and everywhere else.

function Tickets() {
const quantity = Cell.source(2);
const seatsLeft = Cell.derived(() => 12 - quantity.get());
const total = Cell.derived(() => quantity.get() * 48);
const add = () => quantity.set(quantity.get() + 1);
return (
<>
<button onClick={add}>Add a ticket</button>
<p>{seatsLeft} of 12 seats left</p>
<button type="submit">Pay ${total}</button>
</>
);
}
State changes and your interface follows.
In Retend, state is something you can pass around and render directly. Change it anywhere, and everything that depends on it stays up to date.

12 of 12 cities
- AccraGhana
- Buenos AiresArgentina
- Cape TownSouth Africa
- IstanbulTürkiye
- KyotoJapan
- LagosNigeria
- LisbonPortugal
- MarrakeshMorocco
- Mexico CityMexico
- NairobiKenya
- OsloNorway
- VancouverCanada
Change state anywhere
query and order are Cells. The search field and the sort button set them, and everything that reads them updates.
Plain functions and plain JavaScript, top to bottom.
Just write the interface.
Components are functions. State is ordinary JavaScript you can read and change. Events are events. JSX describes what belongs on screen.

import { Cell, For, If } from 'retend';
import { Input } from 'retend-utils/components';
export function TaskList() {
const draft = Cell.source('');
const tasks = Cell.source([{ id: 1, title: 'Ship the docs', done: false }]);
const remaining = Cell.derived(
() => tasks.get().filter((task) => !task.done).length
);
const addTask = () => {
const title = draft.get().trim();
if (!title) return;
tasks.set([...tasks.get(), { id: Date.now(), title, done: false }]);
draft.set('');
};
return (
<form onSubmit--prevent={addTask}>
<Input type="text" model={draft} />
<ul>
{For(tasks, (task) => <li>{task.title}</li>, { key: 'id' })}
</ul>
{If(remaining, {
true: () => <p>{remaining} left to do</p>,
false: () => <p>All done.</p>,
})}
</form>
);
}No dependency arrays
A derived value finds the state it reads by itself. Read tasks inside it, and it recomputes when tasks changes.
Not just for the web.
One app, many renderers
Retend was built without tying its programming model to the browser. Build for the DOM today, a native GPUI window tomorrow, or create a renderer for something entirely different.
Enough to build the whole app.

import { Router, defineRoutes, lazy } from 'retend/router';
const routes = defineRoutes([
{ path: '/', component: Home },
{
path: '/dashboard',
component: DashboardLayout,
children: [
{ path: 'settings', component: lazy(() => import('./Settings')) },
],
},
]);
export const router = new Router({ routes, middlewares: [auth] });- Nested routes
- Lazy loading
- Middleware
- View transitions
Routing
Describe your pages as a tree of routes. Layouts wrap the pages inside them, a page can load the first time someone visits it, and middleware can redirect before anything renders.
Pick a surface. Start building.
Somewhere else entirely? Build a renderer and take Retend with you.
Read the docs
- Getting startedCreate a project and render your first component.
- Reactivity and CellsSource values, derived values, and how updates travel.
- JSX and componentsWrite components as functions and compose them.
- RoutingDefine routes, nest layouts, and move between pages.
- Rendering architectureHow Retend talks to a renderer, and how to write one.
- GPUIBuild and package native desktop apps with retend-gpui.



