TSRX: Statement-Based Component Syntax as a Successor to JSX
TSRX (TypeScript Render Extensions) is a TypeScript superset for writing UI components. It was created by Dominic Gannaway, creator of Ripple.js, and extracted from that framework as a standalone, framework-agnostic tool. It compiles to React, Preact, Solid, and Vue.
The core distinction from JSX is that TSRX is statement-based rather than expression-based. Components do not require a return statement, and control flow uses native JavaScript statements (if/else, for...of, switch, try/catch) rather than expressions (ternaries, .map(), logical &&).
Setup with Vite
TSRX requires a compilation step. Component files use the .tsrx extension. Setup for a React project:
Add the plugin to vite.config.ts before the React plugin:
Core syntax differences
The component keyword and absent return
A TSRX component uses the component keyword instead of function, and the markup is written directly in the component body without a return statement:
The component body renders top-to-bottom. Static text strings are wrapped in double quotes. Class attributes use class rather than className.
return still works for early exits (guard clauses):
Conditional rendering with if/else and switch
JSX requires ternary operators for conditional rendering. TSRX uses standard if/else:
React TSX:
typescript
function Status({ state }: { state: 'online' | 'away' | 'offline' }) {
return (
<div className="card">
{state === 'online' ? (
<span className="ok">Online</span>
) : state === 'away' ? (
<span className="warn">Away</span>
) : (
<span className="off">Offline</span>
)}
</div>
);
}
TSRX:
typescript
[label Status.tsrx]
component Status({ state }: { state: 'online' | 'away' | 'offline' }) {
<div class="card">
if (state === "online") {
<span class="ok">"Online"</span>
} else if (state === "away") {
<span class="warn">"Away"</span>
} else {
<span class="off">"Offline"</span>
}
</div>
}
switch statements can also be used directly without wrapping them in an immediately invoked function expression.
List rendering with for...of
Instead of .map(), TSRX uses a for...of loop with extended syntax:
index iprovides the loop iteration index asikey todo.idis the stable identifier equivalent to React'skeypropcontinueskips rendering an item, providing inline filtering without a separate.filter()call
Async boundaries and error handling
TSRX provides try/catch/pending blocks for async and error handling:
The compiler transforms pending into the appropriate primitive for the target framework. For React, it generates <Suspense> and an error boundary. In standard React, both of these require separate, verbose implementations.
Hook calls inside conditions and loops
React's Rules of Hooks prohibit calling hooks inside conditions or loops. TSRX removes this restriction from the developer's perspective:
The TSRX compiler hoists all hook calls to the top of the generated function before passing it to React. React still receives hooks in a stable order, satisfying its requirements. The developer writes hooks co-located with the markup and logic they relate to.
Scoped styles
The .card styles in Card apply only to elements inside Card. They do not affect the div in Notice despite the shared class name. The compiler adds a unique attribute or hashed class name at build time to scope each component's styles.
Tradeoffs
TSRX introduces a new syntax and a required compilation step. Developers with strong JSX habits may find for...of loops and statement-based rendering unfamiliar at first. The technology is relatively new, which means smaller ecosystem, fewer examples, and less tooling support compared to JSX. AI code generation tools are heavily trained on JSX patterns, so generated suggestions may not match TSRX conventions.
The compiler-hoisted hooks feature is powerful but adds a layer of indirection between what the developer writes and what React receives. If generated output differs from expectations, debugging requires understanding the compilation step.
Final thoughts
TSRX is a well-considered attempt to make component syntax more consistent with the JavaScript developers already write. The if/else, for...of, and try/catch patterns are more readable to developers new to a codebase than nested ternaries and IIFE-wrapped switches. The hook hoisting is technically sound and removes a frequently cited pain point.
Whether the readability benefits outweigh the cost of adopting a new compilation step and syntax depends on the team. For projects already using single-file component frameworks like Svelte or Vue, TSRX's philosophy will feel familiar. For teams with heavy JSX investment, the migration cost is real.
Documentation and framework plugins are at tsrx.dev.