# Ripple - AI/LLM Documentation
## Overview
Ripple is a TypeScript-first UI framework and runtime for `.tsrx` files. TSRX is
the shared source language; Ripple is the target that provides fine-grained
reactivity, DOM rendering, server modules, hydration, context, portals, and
reactive collections.
When answering Ripple questions:
- Prefer `.tsrx` component examples.
- Return direct JSX for single-root components: `function Component(props) { return
; }`.
- Use JSX statement containers (`@{...}`) when TypeScript setup belongs next to the rendered output.
- Use JSX text for static text and `{expr}` for dynamic values.
- When a statement container mixes TypeScript setup with rendered output, put
setup first and finish it with one JSX element, JSX fragment, or JSX
control-flow expression. It cannot finish with a bare expression container,
and script statements cannot appear after the final output.
- Use `@if`, `@for`, `@switch`, and `@try` for rendered control flow. Their bodies
are implicit statement containers and must use `{...}` blocks.
- Keep runtime API guidance Ripple-specific, but keep syntax guidance aligned
with TSRX.
For target-neutral TSRX syntax, see https://tsrx.dev/llms.txt.
## Install
```bash
npm install ripple @ripple-ts/vite-plugin
```
```ts
// vite.config.ts
import { defineConfig } from 'vite';
import ripple from '@ripple-ts/vite-plugin';
export default defineConfig({
plugins: [ripple()],
});
```
Mount client apps with `mount()`.
```ts
import { mount } from 'ripple';
import { App } from './App.tsrx';
mount(App, {
target: document.getElementById('root'),
props: { title: 'Hello world' },
});
```
Use `hydrate()` for server-rendered HTML that should become interactive.
```ts
import { hydrate } from 'ripple';
import { App } from './App.tsrx';
hydrate(App, {
target: document.getElementById('root'),
});
```
## Components
Components are TypeScript functions. Return a JSX element directly when there is
one root, use a fragment for real multiple-child output, and use a JSX statement
container (`@{...}`) when setup statements belong next to the UI.
```tsrx
export function Button({ text, onClick }: { text: string; onClick: () => void }) {
return {text} ;
}
export function App() {
return console.log('saved')} />;
}
```
When setup code and rendered output share the same scope, use `@{...}`. Setup
comes first and the statement container finishes with one output node: a JSX
element, JSX fragment, or JSX control-flow expression.
```tsrx
import { track } from 'ripple';
export function Counter() @{
let &[count] = track(0);
function increment() {
count++;
}
Count: {count}
}
```
If output needs multiple siblings, text, or expression containers after setup,
wrap that output in a fragment. Plain text between tags is JSX text, not
JavaScript.
```tsrx
export function LiteralText() {
return
x = 123
;
}
```
## Reactivity
Create reactive state with `track()` and lazy destructuring. Reading a lazy
binding subscribes to it; assigning to it updates the tracked value.
```tsrx
import { effect, track, type Tracked } from 'ripple';
export function Counter() @{
let &[count, countTracked] = track(0);
let &[double] = track(() => count * 2);
effect(() => {
console.log('count changed', count);
});
<>
Count: {count}
Double: {double}
count++}>Increment
>
}
function CounterValue({ count }: { count: Tracked }) {
return Shared value: {count.value}
;
}
```
You can also keep the tracked object and read or write `.value` directly.
```tsrx
import { track } from 'ripple';
export function Counter() @{
const count = track(0);
count.value++}>{count.value}
}
```
Use `untrack(fn)` when an effect or derived value needs to read something without
subscribing to it.
## Reactive Collections
Use Ripple collection classes when collection operations should update rendered
output.
```tsrx
import { RippleArray, RippleMap, RippleObject, RippleSet } from 'ripple';
export function Inventory() @{
const products = new RippleArray(
{ id: 1, name: 'Jacket' },
{ id: 2, name: 'Boots' },
);
const prices = new RippleMap([[1, 120], [2, 95]]);
const selected = new RippleSet();
const totals = new RippleObject({ added: 0 });
<>
@for (const product of products; key product.id) {
{product.name}: ${prices.get(product.id)}
}
selected.add(1)}>Select jacket
Selected: {selected.size + totals.added}
>
}
```
Available collection APIs include `new RippleArray(...)`, `RippleArray.from(...)`,
`RippleArray.of(...)`, `new RippleObject(...)`, `new RippleMap(...)`, and
`new RippleSet(...)`.
## Template Control Flow
Rendered control flow uses directive expressions.
```tsrx
import { track } from 'ripple';
export function StatusBadge() @{
let &[status] = track<'idle' | 'loading' | 'done'>('idle');
<>
@switch (status) {
@case 'loading': {
Loading...
}
@case 'done': {
Done
}
@default: {
Idle
}
}
(status = 'done')}>Finish
>
}
```
Use `@for (... of ...)` for rendered lists. Filter the iterable before rendering
when some items should be skipped, and use `@empty { ... }` for the no-items
fallback. Direct `continue`, `break`, and `return` statements are not allowed in
`@for` template loop bodies, and are also invalid inside `@if` template branches.
`@switch` cases use `@case` and `@default` clauses with isolated `{...}` blocks.
They do not fall through, and do not use `break` or `return`.
```tsrx
export function UserList({ users }: { users: User[] }) @{
const visibleUsers = users.filter((user) => !user.hidden);
@for (const user of visibleUsers; index i; key user.id) {
{i + 1}. {user.name}
} @empty {
No users
}
}
```
Use ordinary TypeScript `return` for true component exits in setup code.
```tsrx
export function Profile({ user }: { user: User | null }) @{
if (!user) {
return null;
}
{user.name}
}
```
`@try` provides pending and error UI.
```tsrx
export function ProfileBoundary() @{
@try {
} @pending {
Loading...
} @catch (error, reset) {
Error: {error.message}
reset()}>Try again
}
}
```
## Events And Refs
Events use JSX-style handler props. DOM refs can be callback refs, tracked refs,
or local variables.
```tsrx
import { track } from 'ripple';
export function SearchBox() @{
let &[query] = track('');
let input: HTMLInputElement | undefined;
<>
Search
{
query = event.currentTarget.value;
}}
/>
input?.focus()}>Focus
>
}
```
## Dynamic Components And Elements
Use the dynamic tag syntax `<{expression}>` when the element tag or component
constructor is chosen at runtime. The expression can be a string tag name, a
component, or a tracked variable holding either. The closing tag repeats the
same expression: `{expression}>`. Ripple host elements use `class`, not
React's `className`.
```tsrx
import { track } from 'ripple';
type Tag = 'section' | 'article';
function Summary() {
return Summary
;
}
function Details() {
return Details ;
}
export function DynamicPanel() @{
let &[tag] = track('section' as Tag);
let &[Body] = track(() => Summary);
<>
<{tag} class="panel">
<{Body} />
{tag}>
{
tag = tag === 'section' ? 'article' : 'section';
Body = Body === Summary ? Details : Summary;
}}>
Swap
>
}
```
The tag expression must resolve to an element name: an identifier, member
access, static string, or a runtime expression composed of those. Calls,
spreads, string concatenation, and string interpolation are not valid tag
names. Do not use removed dynamic tag syntax such as `<@tag />`,
`<@Component />`, or the imported `Dynamic` component with an `is` prop. Use
`<{tag}>` instead.
## Styles
`
>
}
```
Use `:global(...)` to opt out of scoping. Module-scope style expressions expose
scoped class names that can be passed as props.
```tsrx
const styles = ;
export function Badge() {
return New ;
}
```
## Context
Use `new Context(defaultValue?)`, `set(value)`, and `get()` to share state through
the component tree.
```tsrx
import { Context, track, type Tracked } from 'ripple';
const ThemeContext = new Context>();
export function App() @{
let &[theme, themeTracked] = track('light');
ThemeContext.set(themeTracked);
<>
(theme = theme === 'light' ? 'dark' : 'light')}>
Toggle theme
>
}
function ThemeLabel() @{
const theme = ThemeContext.get();
Theme: {theme.value}
}
```
## Portals
Use `Portal` to render content outside the normal DOM position.
```tsrx
import { Portal, track } from 'ripple';
export function ModalDemo() @{
let &[open] = track(false);
<>
(open = true)}>Open
@if (open) {
Modal content
(open = false)}>Close
}
>
}
```
## Server Modules
`module server { ... }` declares server-only exports in a `.tsrx` module. Import
from `server` in the same file to call those exports from client code through the
Ripple server integration.
```tsrx
module server {
export async function saveName(name: string) {
return { ok: true, name };
}
}
import { saveName } from server;
import { track } from 'ripple';
export function Form() @{
let &[name] = track('');
let &[saved] = track('');
const submit = async () => {
const result = await saveName(name);
saved = result.name;
};
<>
(name = event.currentTarget.value)} />
Save
Saved: {saved}
>
}
```
## Raw HTML
Use `innerHTML={trustedHtml}` for trusted markup. Do not pass untrusted strings to
raw HTML props.
```tsrx
export function Article({ markup }: { markup: string }) {
return ;
}
```
## Do And Do Not
Do:
- Use `.tsrx` for component files.
- Return single-root components directly in examples.
- Use `@{...}` when setup appears next to rendered output, and finish the
statement container with one JSX element, JSX fragment, or JSX control-flow expression.
- Use JSX text for literal children.
- Use `@for` for rendered lists and `@if` for conditional rendering.
- Use Ripple collections for reactive collection state.
Do not:
- Use quoted-string text children as examples.
- Use raw `if`, `for`, `switch`, or `try` as rendered control flow.
- Treat plain text inside a template as JavaScript; it is JSX text.
- Put JavaScript expressions inside `