` objects in any standard JavaScript object, like arrays:
```ripple
import { track } from 'ripple';
let &[first, firstTracked] = track(0);
let &[second, secondTracked] = track(0);
const arr = [firstTracked, secondTracked];
const &[total] = track(() => arr.reduce((a, b) => a + b, 0));
console.log(total);
```
Like shown in the above example, you can compose normal arrays with reactivity and pass them through props or boundaries.
However, if you need the entire array to be fully reactive, including when new elements get added, you should use the reactive array that Ripple provides.
#### Fully Reactive Array
`RippleArray` class from Ripple extends the standard JS `Array` class, and supports all of its methods and properties. Import `RippleArray` from `'ripple'` to use it. All elements existing or new of the `RippleArray` are reactive and respond to the various array operations such as push, pop, shift, unshift, etc. Even if you reference a non-existent element, once it added, the original reference will react to the change.
```ripple
import { RippleArray } from 'ripple';
// using the constructor
const arr = new RippleArray(1, 2, 3);
// using static from method
const arr = RippleArray.from([1, 2, 3]);
// using static fromAsync method
const arr = RippleArray.fromAsync([1, 2, 3]);
// using static of method
const arr = RippleArray.of(1, 2, 3);
```
Usage Example:
```ripple
import { RippleArray } from 'ripple';
export function App() {
const items = new RippleArray(1, 2, 3);
return
"Length: "{items.length}
// Reactive length
for (const item of items) {
{item}
}
items.push(items.length + 1)}>"Add"
;
}
```
#### Reactive Object
`RippleObject` class extends the standard JS `Object` class, and supports all of its methods and properties. Import `RippleObject` from `'ripple'` to use it. `RippleObject` fully supports shallow reactivity and any property on the root level is reactive. You can even reference non-existent properties and once added the original reference reacts to the change.
```ripple
import { RippleObject } from 'ripple';
// using the constructor
const arr = new RippleObject({a: 1, b: 2, c: 3});
```
Usage Example:
```ripple
import { RippleObject } from 'ripple';
export function App() {
const obj = new RippleObject({a: 0})
obj.a = 0;
return <>
"obj.a is: "{obj.a}
"obj.b is: "{obj.b}
{ obj.a++; obj.b = obj.b ?? 5; obj.b++; }}>"Increment"
>;
}
```
#### Reactive Set
```ripple
import { RippleSet } from 'ripple';
function SetExample() {
const mySet = new RippleSet([1, 2, 3]);
return
"Size: "{mySet.size}
// Reactive size
"Has 2: "{mySet.has(2)}
mySet.add(4)}>"Add 4"
;
}
```
#### Reactive Map
The `RippleMap` extends the standard JS `Map` class, and supports all of its methods and properties.
```ripple
import { RippleMap } from 'ripple';
const map = new RippleMap([[1,1], [2,2], [3,3], [4,4]]);
```
RippleMap's reactive methods or properties can be used directly or assigned to reactive variables.
```ripple
import { RippleMap, track } from 'ripple';
export function App() {
const map = new RippleMap([[1,1], [2,2], [3,3], [4,4]]);
return <>
// direct usage
"Direct usage: map has an item with key 2: "{map.has(2)}
// reactive assignment
let &[has] = track(() => map.has(2));
"Assigned usage: map has an item with key 2: "{has}
map.delete(2)}>"Delete item with key 2"
map.set(2, 2)}>"Add key 2 with value 2"
>;
}
```
#### Reactive Date
The `RippleDate` extends the standard JS `Date` class, and supports all of its methods and properties.
```ripple
import { RippleDate } from 'ripple';
const date = new RippleDate(2026, 0, 1); // January 1, 2026
```
RippleDate's reactive methods or properties can be used directly or assigned to reactive variables. All getter methods (`getFullYear()`, `getMonth()`, `getDate()`, etc.) and formatting methods (`toISOString()`, `toDateString()`, etc.) are reactive and will update when the date is modified.
```ripple
import { RippleDate, track } from 'ripple';
export function App() {
const date = new RippleDate(2025, 0, 1, 12, 0, 0);
return <>
// direct usage
"Direct usage: Current year is "{date.getFullYear()}
"ISO String: "{date.toISOString()}
// reactive assignment
let &[year] = track(() => date.getFullYear());
let &[month] = track(() => date.getMonth());
"Assigned usage: Year "{year}", Month "{month}
date.setFullYear(2027)}>"Change to 2027"
date.setMonth(11)}>"Change to December"
>;
}
```
## Advanced Features
### Portal Component
The `Portal` component allows you to render (teleport) content anywhere in the DOM tree, breaking out of the normal component hierarchy. This is particularly useful for modals, tooltips, and notifications.
```ripple
import { Portal } from 'ripple';
export function App() {
return
"My App"
{/* This will render inside document.body, not inside the .app div */}
"I am rendered in document.body!"
"This content escapes the normal component tree."
;
}
```
### Untracking Reactivity
```ripple
import { track, effect, untrack } from 'ripple';
let &[count] = track(0);
let &[double] = track(() => count * 2);
let &[quadruple] = track(() => double * 2);
effect(() => {
// This effect will never fire again, as we've untracked the only dependency it has
console.log(untrack(() => quadruple));
})
```
### Prop Shortcuts
```ripple
// Object spread
"Content"
// Shorthand props (when variable name matches prop name)
"Content"
// Equivalent to:
"Content"
```
### Raw HTML
All text nodes are escaped by default in Ripple. To render trusted raw HTML
strings, use the native `innerHTML` prop:
` `.
When raw markup must sit beside normal children, import `Fragment` from
`ripple` and render ` `.
```tsrx
function Article({ markup }: { markup: string }) {
return ;
}
```
### Text Expressions
Direct double-quoted children are static escaped text. Dynamic text is just a
normal `{expression}`. When you need explicit string coercion, write it in
JavaScript with `String(value)`, `value + ''`, or a typed string value.
```ripple
export function Frame({ children }) {
return
"before"
{children}
"after"
;
}
```
Regular text expressions are HTML-escaped by the target renderer. The content is
never parsed as HTML unless you use the framework's raw HTML prop.
```ripple
export function App() {
const markup = 'Not HTML ';
// Renders the literal string "Not HTML " as text
return {markup}
;
}
```
## TypeScript Integration
### Component Types
```typescript
import type { Component } from 'ripple';
interface Props {
value: string;
label: string;
children?: Component;
}
function MyComponent(props: Props) {
return <>
// Component implementation
>;
}
```
### Context Types
```typescript
import { Context } from 'ripple';
type Theme = 'light' | 'dark';
const ThemeContext = new Context('light');
```
## File Structure
```
src/
App.tsrx # Main app component
components/
Button.tsrx # Reusable components
Card.tsrx
index.ts # Entry point with mount()
```
## Development Tools
### VSCode Extension
- **Name**: "Ripple for VS Code"
- **ID**: `Ripple-TS.ripple-ts-vscode-plugin`
- **Features**: Syntax highlighting, diagnostics, TypeScript integration, IntelliSense
### Vite Plugin
```typescript
// vite.config.js
import { defineConfig } from 'vite';
import ripple from '@ripple-ts/vite-plugin';
export default defineConfig({
plugins: [ripple()]
});
```
### Prettier Plugin
```javascript
// .prettierrc
{
"plugins": ["@tsrx/prettier-plugin"]
}
```
### TSRX MCP Server
Use `@tsrx/mcp` when an AI coding tool supports Model Context Protocol (MCP) and
needs target-aware TSRX help. The server exposes current TSRX documentation,
target detection, project inspection, formatting, compilation, diagnostic
analysis, and read-only file validation.
Hosted endpoint:
For hosted MCP apps and connectors, use:
```text
https://mcp.tsrx.dev/mcp
```
This is the best starting point for ChatGPT web and other clients that connect to
an HTTP MCP server instead of launching a local command.
ChatGPT setup:
Add `https://mcp.tsrx.dev/mcp` when creating a custom app or connector from
ChatGPT developer mode, then select that app from the tools menu in a chat.
Self-hosting:
To self-host the endpoint, deploy the monorepo's `website-mcp` app anywhere that
can run a Node HTTP server and connect remote MCP clients to its `/mcp` route.
Local MCP server:
For local repository work in Codex, Cursor, Gemini CLI, or Claude Code, install
`@tsrx/mcp` as a stdio MCP server so the tool can inspect your project.
Generic local MCP client config:
```json
{
"mcpServers": {
"tsrx": {
"command": "npx",
"args": ["-y", "@tsrx/mcp"]
}
}
}
```
Codex setup:
```toml
# ~/.codex/config.toml
[mcp_servers.tsrx]
command = "npx"
args = ["-y", "@tsrx/mcp"]
```
Gemini CLI setup:
Put the generic `mcpServers` JSON in `.gemini/settings.json` for a project, or
`~/.gemini/settings.json` globally.
Cursor setup:
Put the generic `mcpServers` JSON in `.cursor/mcp.json` for a project, or
`~/.cursor/mcp.json` globally. Cursor Agent and `cursor-agent` discover the
configured tools automatically.
Claude Code setup:
```bash
claude mcp add tsrx -- npx -y @tsrx/mcp
```
Recommended agent workflow:
- Start with `inspect-project` to identify the active runtime target, installed
TSRX packages, formatting/typecheck scripts, and likely project commands.
- Use `detect-target` when only the runtime target is needed.
- For generated source, run `format-tsrx`, then `compile-tsrx`. If compilation
fails, run `analyze-tsrx`, apply the advice, format again, and compile again.
- For existing `.tsrx` files, prefer `validate-tsrx-file`; it reads the file and
runs formatting, compilation, and diagnostic advice in one read-only pass.
- Fetch `tsrx://docs/{slug}.md` resources for target-neutral syntax details and
`tsrx://targets/{target}.md` for runtime-specific handoff guidance.
The MCP server is target-neutral. Runtime-specific imports, bundler setup, and
framework semantics should come from the detected target package and its docs.
## Key Differences from Other Frameworks
### vs React
- No wrapper components for control flow - returned templates support inline `if`, `for`, and `switch`
- Built-in reactivity with `track` and `&[]` lazy destructuring syntax instead of useState/useEffect
- Scoped CSS without CSS-in-JS libraries
- No virtual DOM - fine-grained reactivity
### vs Svelte
- TypeScript-first approach
- JSX-like syntax instead of HTML templates
- Default `.tsrx` extension instead of `.svelte`
- Similar reactivity concepts but different syntax
### vs Solid
- Components are ordinary functions that return TSRX
- Built-in collections (RippleArray, RippleSet)
- Returned templates support statement-style control flow
## Best Practices
1. **Reactivity**: Use `track()` (imported from `'ripple'`) with `&[]` lazy destructuring to create reactive variables
2. **Strings**: Use direct double-quoted children for static text, and `{...}` for JavaScript expressions
3. **Effects**: Use `effect()` (imported from `'ripple'`) for side effects, not direct reactive variable access
4. **Components**: Keep components focused and use TypeScript interfaces for props
5. **Styling**: Use scoped static `