back to home

Ripple-TS / ripple

the elegant TypeScript UI framework

6,979 stars
261 forks
13 issues
JavaScriptTypeScriptPython

AI Architecture Analysis

This repository is indexed by RepoMind. By analyzing Ripple-TS/ripple in our AI interface, you can instantly generate complete architecture diagrams, visualize control flows, and perform automated security audits across the entire codebase.

Our Agentic Context Augmented Generation (Agentic CAG) engine loads full source files into context, avoiding the fragmentation of traditional RAG systems. Ask questions about the architecture, dependencies, or specific features to see it in action.

Embed this Badge

Showcase RepoMind's analysis directly in your repository's README.

[![Analyzed by RepoMind](https://img.shields.io/badge/Analyzed%20by-RepoMind-4F46E5?style=for-the-badge)](https://repomind-ai.vercel.app/repo/Ripple-TS/ripple)
Preview:Analyzed by RepoMind

Repository Summary (README)

Preview
<a href="https://ripplejs.com"> <picture> <source media="(min-width: 768px)" srcset="assets/ripple-desktop.png"> <img src="assets/ripple-mobile.png" alt="Ripple - the elegant TypeScript UI framework" /> </picture> </a>

CI Discord Open in StackBlitz

Ripple TS

Ripple is a TypeScript UI framework that combines the best parts of React, Solid, and Svelte. Created by @trueadm, who has contributed to Inferno, React, Lexical, and Svelte 5.

Key Philosophy: Ripple is TS-first with its own .ripple file extension, allowing seamless TypeScript integration and a unique syntax that enhances both human and LLM developer experience.

📚 Full Documentation | 🎮 Interactive Playground

Features

  • Fine-grained Reactivity: track and @ syntax with a unique reactivity system
  • 🔥 Performance: Industry-leading rendering speed, bundle size, and memory usage
  • 📦 Reactive Collections: #[] arrays and #{} objects with full reactivity
  • 🎯 TypeScript First: Complete type safety with .ripple file extension
  • 🛠️ Developer Tools: VSCode extension, Prettier, and ESLint support
  • 🎨 Scoped Styling: Component-level CSS with automatic scoping

Note: SSR support is coming soon! Currently SPA-only.

🚀 Quick Start

Using CLI (Recommended)

npx create-ripple
cd my-app
npm install && npm run dev

Using Template

npx degit Ripple-TS/ripple/templates/basic my-app
cd my-app
npm install && npm run dev

Add to Existing Project

npm install ripple @ripple-ts/vite-plugin

Note: You can use npm, pnpm, yarn, or bun package managers.

→ Full Installation Guide

Mounting Your App

// index.ts
import { mount } from 'ripple';
import { App } from './App.ripple';

mount(App, {
  props: { title: 'Hello world!' },
  target: document.getElementById('root'),
});

🔧 VSCode Extension

Install the Ripple VSCode extension for:

  • Syntax highlighting
  • TypeScript integration
  • Real-time diagnostics
  • IntelliSense autocomplete

→ Editor Setup Guide

Core Concepts

Components

Define components with the component keyword. Unlike React, you don't return JSX—you write it directly:

component Button(props: { text: string, onClick: () => void }) {
  <button onClick={props.onClick}>
    {props.text}
  </button>
}

export component App() {
  <Button text="Click me" onClick={() => console.log("Clicked!")} />
}

→ Component Guide

Reactivity

Create reactive state with track and access it with the @ operator:

import { track } from 'ripple';

export component App() {
  let count = track(0);

  <div>
    <p>{"Count: "}{@count}</p>
    <button onClick={() => @count++}>{"Increment"}</button>
  </div>
}

Derived values automatically update:

import { track } from 'ripple';

export component App() {
  let count = track(0);
  let double = track(() => @count * 2);
  let quadruple = track(() => @double * 2);

  <div>
    <p>{"Count: "}{@count}</p>
    <p>{"Double: "}{@double}</p>
    <p>{"Quadruple: "}{@quadruple}</p>
    <button onClick={() => @count++}>{"Increment"}</button>
  </div>
}

Reactive collections with shorthand syntax:

export component App() {
  const items = #[1, 2, 3];  // TrackedArray
  const obj = #{a: 1, b: 2}; // TrackedObject

  <div>
    <p>{"Items: "}{items.join(', ')}</p>
    <p>{"Object: a="}{obj.a}{", b="}{obj.b}{", c="}{obj.c}</p>
    <button onClick={() => items.push(items.length + 1)}>{"Add Item"}</button>
    <button onClick={() => obj.c = (obj.c ?? 0) + 1}>{"Increment c"}</button>
  </div>
}

→ Reactivity Guide

Transporting Reactivity

Pass reactive state across function boundaries:

import { track } from 'ripple';

function createDouble(count) {
  return track(() => @count * 2);
}

export component App() {
  let count = track(0);
  const double = createDouble(count);

  <div>
    <p>{"Double: "}{@double}</p>
    <button onClick={() => @count++}>{"Increment"}</button>
  </div>
}

→ Transporting Reactivity Guide

Effects & Side Effects

import { effect, track } from 'ripple';

export component App() {
  let count = track(0);

  effect(() => {
    console.log('Count changed:', @count);
  });

  <button onClick={() => @count++}>{'Increment'}</button>
}

→ Effects & Reactivity Guide

Control Flow

Conditionals:

import { track } from 'ripple';

export component App() {
  let condition = track(true);

  <div>
    if (@condition) {
      <div>{'True'}</div>
    } else {
      <div>{'False'}</div>
    }
    <button onClick={() => @condition = !@condition}>{"Toggle"}</button>
  </div>
}

Loops:

export component App() {
  const items = #[
    {id: 1, name: 'Item 1'},
    {id: 2, name: 'Item 2'},
    {id: 3, name: 'Item 3'}
  ];

  <div>
    for (const item of items; index i; key item.id) {
      <div>{item.name}{" (index: "}{i}{")"}</div>
    }
    <button onClick={() => items.push({id: items.length + 1, name: `Item ${items.length + 1}`})}>{"Add Item"}</button>
  </div>
}

Error Boundaries:

import { track } from 'ripple';

component ComponentThatMayFail(props: { shouldFail: boolean }) {
  if (props.shouldFail) {
    throw new Error('Component failed!');
    {'This will never render'}
  }

  <div>{"Component working fine"}</div>
}

export component App() {
  let shouldFail = track(false);

  <div>
    try {
      <ComponentThatMayFail shouldFail={@shouldFail} />
    } catch (e) {
      <div>{'Error: ' + e.message}</div>
    }
    <button onClick={() => @shouldFail = !@shouldFail}>{"Toggle Error"}</button>
  </div>
}

→ Control Flow Guide

DOM Refs

Capture DOM elements with the {ref fn} syntax:

export component App() {
  <div {ref (node) => console.log(node)}>{"Hello"}</div>
}

→ DOM Refs Guide

Events

Use React-style event handlers:

import { track } from 'ripple';

export component App() {
  let value = track('');

  <div>
    <button onClick={() => console.log('Clicked')}>{'Click'}</button>
    <input onInput={(e) => @value = e.target.value} />
    <p>{"You typed: "}{@value}</p>
  </div>
}

→ Events Guide

Styling

Scoped CSS:

export component App() {
  <div class="container">{"Content"}</div>

  <style>
    .container {
      padding: 1rem;
      background: lightblue;
      border-radius: 8px;
    }
  </style>
}

Dynamic styles:

import { track } from 'ripple';

export component App() {
  let color = track('red');

  <div>
    <div style={{ color: @color, fontWeight: 'bold' }}>{"Styled text"}</div>
    <button onClick={() => @color = @color === 'red' ? 'blue' : 'red'}>{"Toggle Color"}</button>
  </div>
}

→ Styling Guide

Advanced Features

Context API

Share state across the component tree:

import { Context, track } from 'ripple';

const ThemeContext = new Context();

component Child() {
  const theme = ThemeContext.get();
  <div>{"Theme: " + @theme}</div>
}

export component App() {
  let theme = track('light');

  ThemeContext.set(theme);

  <div>
    <Child />
    <button onClick={() => @theme = @theme === 'light' ? 'dark' : 'light'}>{"Toggle Theme"}</button>
  </div>
}

→ State Management Guide

Portals

Render content outside the component hierarchy:

import { Portal, track } from 'ripple';

export component App() {
  let showModal = track(false);

  <div>
    <button onClick={() => @showModal = !@showModal}>{"Toggle Modal"}</button>

    if (@showModal) {
      <Portal target={document.body}>
        <div class="modal">
          <p>{'Modal content'}</p>
          <button onClick={() => @showModal = false}>{"Close"}</button>
        </div>
      </Portal>
    }
  </div>
}

→ Portal & Component Guide

Resources

Contributing

Contributions are welcome! Please see our contributing guidelines.

License

MIT License - see LICENSE for details.