Ripple: a TypeScript UI framework where a statement container ends in exactly one node
the elegant TypeScript UI framework
At a glance
- What is it?
- Ripple is a TypeScript-first framework built around .tsrx files, with fine-grained reactivity through track() and .value, template-native control flow directives, and scoped styles with automatic class hashing. The interesting parts are the rules that are unusual rather than the feature list. A JSX statement container must end in exactly one output node, return means something different inside a template branch than it does in setup, and passing a tracked value to a child is a write-permission decision.
- Who is it for?
- Adopt it if you want TypeScript setup code next to the markup and fine-grained reactivity, and if you are willing to learn a template dialect with rules of its own. Do not adopt it if your team cannot absorb a second syntax for control flow, or if you plan to contribute on a runtime older than Node 22, because the monorepo declares strict engine requirements.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 16 days ago.
- What is it written in?
- Mainly JavaScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- Ripple-TS/ripple7,409 ★ · +9
| Repository | 2026-09-22 | 2026-11-14 |
|---|---|---|
| Ripple-TS/ripple | 7.4k | 7.4k |
9 Stars in the last 53 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
A statement container must end in exactly one output node
This is the rule that decides whether your component compiles, and the README states it three separate ways. A component with one root returns a JSX element directly. When setup statements or multiple rendered siblings belong next to the UI, you use a JSX statement container, written as an at sign and braces, where the setup comes first and the container finishes with exactly one output node: a JSX element, a JSX fragment, or a JSX control-flow expression. If the output needs text, expression containers, or several siblings after the setup, wrap them in a fragment. The related trap is spelled out too: text such as an assignment between tags is JSX text rather than JavaScript, unless it is inside a statement container. Nested scopes follow the same rule, which the cart example demonstrates with a subtotal and a discount computed before a fragment of two paragraphs.
Inside a template branch, return and break are not what you think
Control flow is written with directive-prefixed expressions rather than keywords: conditional rendering, loops with an index and a key, an empty branch for loops that render nothing, and a try construct with pending and catch branches where the catch handler receives the error and a reset function. The rule that will surprise people is the separation between the two kinds of exit. Ordinary `return` is for real function exits inside TypeScript setup. A direct `return`, `continue` or `break` is not valid inside a conditional template branch. The dashboard example shows both in one file: it returns null early when there is no user, and then renders a fragment. So a component that wants to bail out does it in setup, and a component that wants to choose between two renderings does it with a template branch. Comments are also handled explicitly, since JavaScript comments between template children are allowed and are not rendered.
Passing a tracked value to a child is a write-permission decision
State is created with track() and read or written through .value, and the README is clear that reads in templates and effects stay reactive while writes update everything that depends on them. Derived values are made the same way, by tracking a computation. Then comes the part that has no equivalent in most frameworks. A tracked object can be passed through data structures and props, and how you pass it decides what the child may do: pass the tracked object itself when the child may write it, and pass a read-only view when it should only read. That read-only view is produced by a function that returns a derived value which follows the number but rejects writes, the same as track(() => count.value). So the permission model for a component's state is encoded in the props you write.
Node 22, pnpm 11.15.1 and npm 11, with strict engines turned on
The workspace root is where this project's requirements live, and they are explicit. The package manager is pinned, and the engines block names three floors: a Node version of 22 or newer, that exact pnpm version, and npm at 11.6.2 or newer. The manifest then sets a strict engines flag, so the requirements are enforced rather than advisory. That is stricter than most libraries and it will bite you on an older corporate runtime, but it is coherent with a project that compiles templates and ships its own plugin packages. The install paths are three, and the first is a one-off generator:
npx create-ripple
cd my-app
npm install
npm run devA template can be fetched instead of generated, and adding the framework to an existing project means installing the runtime and its Vite plugin together. The README also says to use npm, pnpm, yarn or bun, matching your project.
Ten packages are typechecked in one command, and adapters ship per environment
The typecheck script is a single line that runs the type checker across thirteen project configurations plus a TanStack Router package typecheck, and reading it is the fastest map of the repository. It covers the runtime package, the TSRX integration package, the command line package, the project generator, a generic adapter, and then three environment-specific adapters for Bun, Node and Vercel, followed by a Rollup plugin and a Vite plugin. That list explains the shape of the project. One compiler, several targets, and separate adapters because the runtime differs; you also get both bundler plugins rather than one, so the framework is not tied to a single build tool. Tests run through a single runner configured at the root, formatting is Prettier with a check script, and there is a benchmark script with a quick mode, which is the shape you would expect from a framework that takes runtime performance seriously.
A changeset check runs before versioning and before publishing
The release scripts are gated, and that is the part worth copying. The changeset command runs the changesets tool and then a repository script that checks the changesets, and both the version and publish scripts run that check first, so a release cannot skip it. The three most recent tags were published within seconds of each other on 2026-10-29: @ripple-ts/[email protected], @ripple-ts/[email protected] and @ripple-ts/[email protected]. The prepare hook is also defensive, since the rules generation command is allowed to fail without blocking installation. Alongside that there is a rules file at the root and a synchronisation tool that can check whether the generated rules match, which is how the project keeps instructions for coding assistants in the repository in step with its own configuration.
The author wrote parts of the frameworks this one argues with
The .tsrx file extension is described as more than a template format. Those files are also a standalone language, and the compiler stack behind them can target React, Preact, Solid, Vue or Ripple, with Ripple described as the runtime-focused target that adds the reactivity primitives, reactive collections, server modules, hydration and DOM helpers. The provenance is on the page too: the framework was created by a developer whose contributions include Inferno, React, Lexical and Svelte 5. That combination explains the design. Fine-grained reactivity, template-native control flow instead of JSX conditionals, and setup code that lives in the same file as the markup are all positions taken by someone who has worked inside the libraries being replaced. The other structural detail is that the repository carries two website directories alongside a playground, so the docs surface is being rebuilt as well as the framework.
Editorial conclusion
Adopt it if you want TypeScript setup code next to the markup and fine-grained reactivity, and if you are willing to learn a template dialect with rules of its own. Do not adopt it if your team cannot absorb a second syntax for control flow, or if you plan to contribute on a runtime older than Node 22, because the monorepo declares strict engine requirements. Before you start, read the statement container rules until they are automatic, decide deliberately whether children may write your tracked values, and try the playground before installing anything locally.
Frequently asked questions
What is Ripple?
Ripple is a TypeScript-first UI framework built around .tsrx files, with fine-grained reactivity through track() and .value, template-native control flow such as @if, @for and @try, scoped style blocks with automatic class hashing, and server-side rendering in buffered and streaming forms plus hydration. Components are ordinary TypeScript functions, and setup statements live in JSX statement containers that must end in exactly one output node.
how to use ripple
Create a project with npx create-ripple, or fetch the basic template, then install and run the dev server. To add it to an existing project, install the runtime and its Vite plugin together, then mount a component with mount, passing props and a target element. State is created with track and read or written through .value, and a read-only view of a tracked value decides whether a child component may write it.
Can Ripple output other frameworks?
The .tsrx files are described as a standalone language, and the shared compiler stack can target React, Preact, Solid, Vue and Ripple. Ripple is the runtime-focused target, adding track(), reactive collections, server modules, hydration and DOM helpers. There is a hosted playground and a StackBlitz template so you can try it before installing anything locally.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ripple-ts-ripple)Star history chart for your README
[](https://hysenlabs.com/projects/ripple-ts-ripple)Was this useful?