Ripple
Ripple is a TypeScript UI framework that takes the best parts of React, Solid and Svelte and combines them into one package.
Ripple components can be used in LiveCodes as documented in the Ripple docs. See below for usage.
Demo
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Ripple
import { createPlayground } from 'livecodes';
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n let doubled = track(() => @count * 2);\n\n <div class='container'>\n <h2>{'Counter'}</h2>\n <p>{`Count: ${@count}`}</p>\n <p>{`Doubled: ${@doubled}`}</p>\n\n <button onClick={() => @count++}>{'Increment'}</button>\n <button onClick={() => @count = 0}>{'Reset'} </button>\n </div>\n\n <style>\n .container {\n text-align: center;\n font-family: \"Arial\", sans-serif;\n }\n\n button {\n font-size: 1em;\n padding: 6px 12px;\n margin: 0 6px;\n cursor: pointer;\n border: none;\n font-family: \"Courier New\", monospace;\n }\n\n button:hover {\n background-color: #e0e0e0;\n }\n </style>\n}"
}
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n let doubled = track(() => @count * 2);\n\n <div class='container'>\n <h2>{'Counter'}</h2>\n <p>{`Count: ${@count}`}</p>\n <p>{`Doubled: ${@doubled}`}</p>\n\n <button onClick={() => @count++}>{'Increment'}</button>\n <button onClick={() => @count = 0}>{'Reset'} </button>\n </div>\n\n <style>\n .container {\n text-align: center;\n font-family: \"Arial\", sans-serif;\n }\n\n button {\n font-size: 1em;\n padding: 6px 12px;\n margin: 0 6px;\n cursor: pointer;\n border: none;\n font-family: \"Courier New\", monospace;\n }\n\n button:hover {\n background-color: #e0e0e0;\n }\n </style>\n}"
}
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n let doubled = track(() => @count * 2);\n\n <div class='container'>\n <h2>{'Counter'}</h2>\n <p>{`Count: ${@count}`}</p>\n <p>{`Doubled: ${@doubled}`}</p>\n\n <button onClick={() => @count++}>{'Increment'}</button>\n <button onClick={() => @count = 0}>{'Reset'} </button>\n </div>\n\n <style>\n .container {\n text-align: center;\n font-family: \"Arial\", sans-serif;\n }\n\n button {\n font-size: 1em;\n padding: 6px 12px;\n margin: 0 6px;\n cursor: pointer;\n border: none;\n font-family: \"Courier New\", monospace;\n }\n\n button:hover {\n background-color: #e0e0e0;\n }\n </style>\n}"
}
}
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n let doubled = track(() => @count * 2);\n\n <div class='container'>\n <h2>{'Counter'}</h2>\n <p>{`Count: ${@count}`}</p>\n <p>{`Doubled: ${@doubled}`}</p>\n\n <button onClick={() => @count++}>{'Increment'}</button>\n <button onClick={() => @count = 0}>{'Reset'} </button>\n </div>\n\n <style>\n .container {\n text-align: center;\n font-family: \"Arial\", sans-serif;\n }\n\n button {\n font-size: 1em;\n padding: 6px 12px;\n margin: 0 6px;\n cursor: pointer;\n border: none;\n font-family: \"Courier New\", monospace;\n }\n\n button:hover {\n background-color: #e0e0e0;\n }\n </style>\n}"
}
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
let options = $state({
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n let doubled = track(() => @count * 2);\n\n <div class='container'>\n <h2>{'Counter'}</h2>\n <p>{`Count: ${@count}`}</p>\n <p>{`Doubled: ${@doubled}`}</p>\n\n <button onClick={() => @count++}>{'Increment'}</button>\n <button onClick={() => @count = 0}>{'Reset'} </button>\n </div>\n\n <style>\n .container {\n text-align: center;\n font-family: \"Arial\", sans-serif;\n }\n\n button {\n font-size: 1em;\n padding: 6px 12px;\n margin: 0 6px;\n cursor: pointer;\n border: none;\n font-family: \"Courier New\", monospace;\n }\n\n button:hover {\n background-color: #e0e0e0;\n }\n </style>\n}"
}
}
});
let container = $state(null);
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>
import { createPlayground, type EmbedOptions } from 'livecodes';
export default function App() {
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n let doubled = track(() => @count * 2);\n\n <div class='container'>\n <h2>{'Counter'}</h2>\n <p>{`Count: ${@count}`}</p>\n <p>{`Doubled: ${@doubled}`}</p>\n\n <button onClick={() => @count++}>{'Increment'}</button>\n <button onClick={() => @count = 0}>{'Reset'} </button>\n </div>\n\n <style>\n .container {\n text-align: center;\n font-family: \"Arial\", sans-serif;\n }\n\n button {\n font-size: 1em;\n padding: 6px 12px;\n margin: 0 6px;\n cursor: pointer;\n border: none;\n font-family: \"Courier New\", monospace;\n }\n\n button:hover {\n background-color: #e0e0e0;\n }\n </style>\n}"
}
}
};
const onMounted = (container: HTMLElement) => {
createPlayground(container, options);
};
return <div ref={onMounted}></div>;
}
import { createPlayground, type EmbedOptions } from 'livecodes';
export default component App() {
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n let doubled = track(() => @count * 2);\n\n <div class='container'>\n <h2>{'Counter'}</h2>\n <p>{`Count: ${@count}`}</p>\n <p>{`Doubled: ${@doubled}`}</p>\n\n <button onClick={() => @count++}>{'Increment'}</button>\n <button onClick={() => @count = 0}>{'Reset'} </button>\n </div>\n\n <style>\n .container {\n text-align: center;\n font-family: \"Arial\", sans-serif;\n }\n\n button {\n font-size: 1em;\n padding: 6px 12px;\n margin: 0 6px;\n cursor: pointer;\n border: none;\n font-family: \"Courier New\", monospace;\n }\n\n button:hover {\n background-color: #e0e0e0;\n }\n </style>\n}"
}
}
};
const onMount = (container: HTMLElement) => {
createPlayground(container, options);
};
<div {ref onMount}></div>
}
Mounting
Auto-rendering
A component is mounted and rendered automatically as a Ripple component (without having to manually mount it) if the following conditions are met:
- The component is exported as the default export.
- No imports from
"./script"in markup editor. - Auto-rendering is not disabled.
Root Element
To mount the application instance to a specific DOM element use "livecodes-app" as the element id in the HTML. Otherwise, if that element is not found, a new div element is added to document.body and is used to mount the instance.
In case you need more control, you can manually mount the instance.
Example:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Ripple
import { createPlayground } from 'livecodes';
const options = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Custom Root Element</h1>\n<div id=\"livecodes-app\"></div>\n<div>...other page content</div>\n"
},
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component App() {\n let name = track('Ripple');\n <div>{`I'm a ${@name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Custom Root Element</h1>\n<div id=\"livecodes-app\"></div>\n<div>...other page content</div>\n"
},
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component App() {\n let name = track('Ripple');\n <div>{`I'm a ${@name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Custom Root Element</h1>\n<div id=\"livecodes-app\"></div>\n<div>...other page content</div>\n"
},
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component App() {\n let name = track('Ripple');\n <div>{`I'm a ${@name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Custom Root Element</h1>\n<div id=\"livecodes-app\"></div>\n<div>...other page content</div>\n"
},
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component App() {\n let name = track('Ripple');\n <div>{`I'm a ${@name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
let options = $state({
"config": {
"markup": {
"language": "html",
"content": "<h1>Custom Root Element</h1>\n<div id=\"livecodes-app\"></div>\n<div>...other page content</div>\n"
},
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component App() {\n let name = track('Ripple');\n <div>{`I'm a ${@name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
});
let container = $state(null);
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>
import { createPlayground, type EmbedOptions } from 'livecodes';
export default function App() {
const options: EmbedOptions = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Custom Root Element</h1>\n<div id=\"livecodes-app\"></div>\n<div>...other page content</div>\n"
},
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component App() {\n let name = track('Ripple');\n <div>{`I'm a ${@name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
const onMounted = (container: HTMLElement) => {
createPlayground(container, options);
};
return <div ref={onMounted}></div>;
}
import { createPlayground, type EmbedOptions } from 'livecodes';
export default component App() {
const options: EmbedOptions = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Custom Root Element</h1>\n<div id=\"livecodes-app\"></div>\n<div>...other page content</div>\n"
},
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component App() {\n let name = track('Ripple');\n <div>{`I'm a ${@name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
const onMount = (container: HTMLElement) => {
createPlayground(container, options);
};
<div {ref onMount}></div>
}
Manual Mount
Exports from Ripple code can be imported in markup editor from "./Component.ripple" and used to mount the application instance manually.
See Exports for details.
Example:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Ripple
import { createPlayground } from 'livecodes';
const options = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Manual Mount</h1>\n<div id=\"root\"></div>\n<div>...other page content</div>\n\n<script type=\"module\">\n import { mount } from 'ripple';\n import { App } from \"./Component.ripple\";\n\n mount(App, {\n props: { name: 'Ripple' },\n target: document.getElementById('root'),\n });\n</script>\n"
},
"script": {
"language": "ripple",
"content": "export component App(props: { name: string }) {\n <div>{`I'm a ${props.name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Manual Mount</h1>\n<div id=\"root\"></div>\n<div>...other page content</div>\n\n<script type=\"module\">\n import { mount } from 'ripple';\n import { App } from \"./Component.ripple\";\n\n mount(App, {\n props: { name: 'Ripple' },\n target: document.getElementById('root'),\n });\n</script>\n"
},
"script": {
"language": "ripple",
"content": "export component App(props: { name: string }) {\n <div>{`I'm a ${props.name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Manual Mount</h1>\n<div id=\"root\"></div>\n<div>...other page content</div>\n\n<script type=\"module\">\n import { mount } from 'ripple';\n import { App } from \"./Component.ripple\";\n\n mount(App, {\n props: { name: 'Ripple' },\n target: document.getElementById('root'),\n });\n</script>\n"
},
"script": {
"language": "ripple",
"content": "export component App(props: { name: string }) {\n <div>{`I'm a ${props.name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Manual Mount</h1>\n<div id=\"root\"></div>\n<div>...other page content</div>\n\n<script type=\"module\">\n import { mount } from 'ripple';\n import { App } from \"./Component.ripple\";\n\n mount(App, {\n props: { name: 'Ripple' },\n target: document.getElementById('root'),\n });\n</script>\n"
},
"script": {
"language": "ripple",
"content": "export component App(props: { name: string }) {\n <div>{`I'm a ${props.name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
let options = $state({
"config": {
"markup": {
"language": "html",
"content": "<h1>Manual Mount</h1>\n<div id=\"root\"></div>\n<div>...other page content</div>\n\n<script type=\"module\">\n import { mount } from 'ripple';\n import { App } from \"./Component.ripple\";\n\n mount(App, {\n props: { name: 'Ripple' },\n target: document.getElementById('root'),\n });\n</script>\n"
},
"script": {
"language": "ripple",
"content": "export component App(props: { name: string }) {\n <div>{`I'm a ${props.name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
});
let container = $state(null);
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>
import { createPlayground, type EmbedOptions } from 'livecodes';
export default function App() {
const options: EmbedOptions = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Manual Mount</h1>\n<div id=\"root\"></div>\n<div>...other page content</div>\n\n<script type=\"module\">\n import { mount } from 'ripple';\n import { App } from \"./Component.ripple\";\n\n mount(App, {\n props: { name: 'Ripple' },\n target: document.getElementById('root'),\n });\n</script>\n"
},
"script": {
"language": "ripple",
"content": "export component App(props: { name: string }) {\n <div>{`I'm a ${props.name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
const onMounted = (container: HTMLElement) => {
createPlayground(container, options);
};
return <div ref={onMounted}></div>;
}
import { createPlayground, type EmbedOptions } from 'livecodes';
export default component App() {
const options: EmbedOptions = {
"config": {
"markup": {
"language": "html",
"content": "<h1>Manual Mount</h1>\n<div id=\"root\"></div>\n<div>...other page content</div>\n\n<script type=\"module\">\n import { mount } from 'ripple';\n import { App } from \"./Component.ripple\";\n\n mount(App, {\n props: { name: 'Ripple' },\n target: document.getElementById('root'),\n });\n</script>\n"
},
"script": {
"language": "ripple",
"content": "export component App(props: { name: string }) {\n <div>{`I'm a ${props.name} component`}</div>\n <style>\n div {\n color: #0e5ca3;\n margin-bottom: 1em;\n }\n </style>\n}\n"
}
}
};
const onMount = (container: HTMLElement) => {
createPlayground(container, options);
};
<div {ref onMount}></div>
}
Disabling Auto-rendering
To disable auto-rendering, set the custom settings disableAutoRender property to true.
{
"ripple": {
"disableAutoRender": true
}
}
Importing Modules
NPM Modules
npm modules can be imported as described in the section about module resolution, including bare module imports and importing from different CDNs. Stylesheets imported in the script block are added as <link rel="stylesheet"> tags in the page head (see below).
Module imports can be customized using import maps as described in module resolution documentations.
Example:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Ripple
import { createPlayground } from 'livecodes';
const options = {
"params": {
"ripple": "import { track, effect } from \"ripple\";\nimport confetti from \"canvas-confetti\";\n\nexport default component App() {\n <div class=\"container\">\n let count = track(0);\n\n effect(() => {\n if (@count === 2) confetti();\n });\n\n <button onClick={() => @count++}>{@count}</button>\n\n if (@count > 1) {\n <div>{'Greater than 1!'}</div>\n }\n </div>\n\n <style>\n button {\n padding: 1rem;\n font-size: 1rem;\n cursor: pointer;\n }\n </style>\n}\n"
},
"formatCode": false
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"params": {
"ripple": "import { track, effect } from \"ripple\";\nimport confetti from \"canvas-confetti\";\n\nexport default component App() {\n <div class=\"container\">\n let count = track(0);\n\n effect(() => {\n if (@count === 2) confetti();\n });\n\n <button onClick={() => @count++}>{@count}</button>\n\n if (@count > 1) {\n <div>{'Greater than 1!'}</div>\n }\n </div>\n\n <style>\n button {\n padding: 1rem;\n font-size: 1rem;\n cursor: pointer;\n }\n </style>\n}\n"
},
"formatCode": false
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"params": {
"ripple": "import { track, effect } from \"ripple\";\nimport confetti from \"canvas-confetti\";\n\nexport default component App() {\n <div class=\"container\">\n let count = track(0);\n\n effect(() => {\n if (@count === 2) confetti();\n });\n\n <button onClick={() => @count++}>{@count}</button>\n\n if (@count > 1) {\n <div>{'Greater than 1!'}</div>\n }\n </div>\n\n <style>\n button {\n padding: 1rem;\n font-size: 1rem;\n cursor: pointer;\n }\n </style>\n}\n"
},
"formatCode": false
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"params": {
"ripple": "import { track, effect } from \"ripple\";\nimport confetti from \"canvas-confetti\";\n\nexport default component App() {\n <div class=\"container\">\n let count = track(0);\n\n effect(() => {\n if (@count === 2) confetti();\n });\n\n <button onClick={() => @count++}>{@count}</button>\n\n if (@count > 1) {\n <div>{'Greater than 1!'}</div>\n }\n </div>\n\n <style>\n button {\n padding: 1rem;\n font-size: 1rem;\n cursor: pointer;\n }\n </style>\n}\n"
},
"formatCode": false
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
let options = $state({
"params": {
"ripple": "import { track, effect } from \"ripple\";\nimport confetti from \"canvas-confetti\";\n\nexport default component App() {\n <div class=\"container\">\n let count = track(0);\n\n effect(() => {\n if (@count === 2) confetti();\n });\n\n <button onClick={() => @count++}>{@count}</button>\n\n if (@count > 1) {\n <div>{'Greater than 1!'}</div>\n }\n </div>\n\n <style>\n button {\n padding: 1rem;\n font-size: 1rem;\n cursor: pointer;\n }\n </style>\n}\n"
},
"formatCode": false
});
let container = $state(null);
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>
import { createPlayground, type EmbedOptions } from 'livecodes';
export default function App() {
const options: EmbedOptions = {
"params": {
"ripple": "import { track, effect } from \"ripple\";\nimport confetti from \"canvas-confetti\";\n\nexport default component App() {\n <div class=\"container\">\n let count = track(0);\n\n effect(() => {\n if (@count === 2) confetti();\n });\n\n <button onClick={() => @count++}>{@count}</button>\n\n if (@count > 1) {\n <div>{'Greater than 1!'}</div>\n }\n </div>\n\n <style>\n button {\n padding: 1rem;\n font-size: 1rem;\n cursor: pointer;\n }\n </style>\n}\n"
},
"formatCode": false
};
const onMounted = (container: HTMLElement) => {
createPlayground(container, options);
};
return <div ref={onMounted}></div>;
}
import { createPlayground, type EmbedOptions } from 'livecodes';
export default component App() {
const options: EmbedOptions = {
"params": {
"ripple": "import { track, effect } from \"ripple\";\nimport confetti from \"canvas-confetti\";\n\nexport default component App() {\n <div class=\"container\">\n let count = track(0);\n\n effect(() => {\n if (@count === 2) confetti();\n });\n\n <button onClick={() => @count++}>{@count}</button>\n\n if (@count > 1) {\n <div>{'Greater than 1!'}</div>\n }\n </div>\n\n <style>\n button {\n padding: 1rem;\n font-size: 1rem;\n cursor: pointer;\n }\n </style>\n}\n"
},
"formatCode": false
};
const onMount = (container: HTMLElement) => {
createPlayground(container, options);
};
<div {ref onMount}></div>
}
External Components
External Ripple components can be imported. The import URL can be either:
- An absolute URL ending with
.rippleextension. - A bare module import for a published npm module with full path to the component with extension.
- A data URL starting with
data:text/ripple.
Any bare or relative imports in the imported files are resolved and compiled recursively. If a processor is enabled (e.g. Tailwind CSS), classes in the imported files are detected and used in the generated CSS (see CSS Frameworks section below).
Example: (source)
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Ripple
import { createPlayground } from 'livecodes';
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "// import from npm modules\nimport { track } from 'ripple';\nimport { Counter } from 'ripple-tailwind-counter-demo/src/Counter.ripple';\n// alternatively use absolute URLs\n// import { Counter } from 'https://raw.githubusercontent.com/hatemhosny/ripple-tailwind-counter-demo/refs/heads/main/src/Counter.ripple';\n\nexport default component App() {\n let name = track(\"World\");\n\n setTimeout(() => {\n @name = \"Ripple + Tailwind!\";\n }, 2000);\n\n <Counter {name} />\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "// import from npm modules\nimport { track } from 'ripple';\nimport { Counter } from 'ripple-tailwind-counter-demo/src/Counter.ripple';\n// alternatively use absolute URLs\n// import { Counter } from 'https://raw.githubusercontent.com/hatemhosny/ripple-tailwind-counter-demo/refs/heads/main/src/Counter.ripple';\n\nexport default component App() {\n let name = track(\"World\");\n\n setTimeout(() => {\n @name = \"Ripple + Tailwind!\";\n }, 2000);\n\n <Counter {name} />\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "// import from npm modules\nimport { track } from 'ripple';\nimport { Counter } from 'ripple-tailwind-counter-demo/src/Counter.ripple';\n// alternatively use absolute URLs\n// import { Counter } from 'https://raw.githubusercontent.com/hatemhosny/ripple-tailwind-counter-demo/refs/heads/main/src/Counter.ripple';\n\nexport default component App() {\n let name = track(\"World\");\n\n setTimeout(() => {\n @name = \"Ripple + Tailwind!\";\n }, 2000);\n\n <Counter {name} />\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "// import from npm modules\nimport { track } from 'ripple';\nimport { Counter } from 'ripple-tailwind-counter-demo/src/Counter.ripple';\n// alternatively use absolute URLs\n// import { Counter } from 'https://raw.githubusercontent.com/hatemhosny/ripple-tailwind-counter-demo/refs/heads/main/src/Counter.ripple';\n\nexport default component App() {\n let name = track(\"World\");\n\n setTimeout(() => {\n @name = \"Ripple + Tailwind!\";\n }, 2000);\n\n <Counter {name} />\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
let options = $state({
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "// import from npm modules\nimport { track } from 'ripple';\nimport { Counter } from 'ripple-tailwind-counter-demo/src/Counter.ripple';\n// alternatively use absolute URLs\n// import { Counter } from 'https://raw.githubusercontent.com/hatemhosny/ripple-tailwind-counter-demo/refs/heads/main/src/Counter.ripple';\n\nexport default component App() {\n let name = track(\"World\");\n\n setTimeout(() => {\n @name = \"Ripple + Tailwind!\";\n }, 2000);\n\n <Counter {name} />\n}\n"
},
"processors": [
"tailwindcss"
]
}
});
let container = $state(null);
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>
import { createPlayground, type EmbedOptions } from 'livecodes';
export default function App() {
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "// import from npm modules\nimport { track } from 'ripple';\nimport { Counter } from 'ripple-tailwind-counter-demo/src/Counter.ripple';\n// alternatively use absolute URLs\n// import { Counter } from 'https://raw.githubusercontent.com/hatemhosny/ripple-tailwind-counter-demo/refs/heads/main/src/Counter.ripple';\n\nexport default component App() {\n let name = track(\"World\");\n\n setTimeout(() => {\n @name = \"Ripple + Tailwind!\";\n }, 2000);\n\n <Counter {name} />\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
const onMounted = (container: HTMLElement) => {
createPlayground(container, options);
};
return <div ref={onMounted}></div>;
}
import { createPlayground, type EmbedOptions } from 'livecodes';
export default component App() {
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "// import from npm modules\nimport { track } from 'ripple';\nimport { Counter } from 'ripple-tailwind-counter-demo/src/Counter.ripple';\n// alternatively use absolute URLs\n// import { Counter } from 'https://raw.githubusercontent.com/hatemhosny/ripple-tailwind-counter-demo/refs/heads/main/src/Counter.ripple';\n\nexport default component App() {\n let name = track(\"World\");\n\n setTimeout(() => {\n @name = \"Ripple + Tailwind!\";\n }, 2000);\n\n <Counter {name} />\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
const onMount = (container: HTMLElement) => {
createPlayground(container, options);
};
<div {ref onMount}></div>
}
Exports
Values exported from script editor (default or named) can be imported in the markup editor by importing from "./script" (with no extension).
This can be useful, for example, for manually mounting components in the markup editor.
When values are imported from "./script", auto-rendering is disabled, because it is assumed that you want to take control over component rendering.
Styles
CSS can be applied to the component using various ways:
Component Styles
Styles in a style tag in a Ripple component are applied to that component only.
CSS processors (e.g. SCSS, Stylus) can be used by specifying a lang attribute for the style tag.
Example:
Style Editor
Styles added in the style editor are applied globally to the result page. This can use different languages/processors supported in LiveCodes including CSS, SCSS, Less, Stylus, ..etc. See style documentation for more details.
And of course, styles and stylesheets added in markup editor are also applied globally.
Importing Stylesheets
Stylesheets imported in script editor are added as <link rel="stylesheet"> tags in the page head.
The stylesheet URL can be an absolute URL or a path in the npm package. The URL has to end with ".css".
example:
CSS Modules
CSS modules are supported and are documented separately. Make sure to enable CSS modules (from style editor menu or in processors property of configuration object).
Demo:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Ripple
import { createPlayground } from 'livecodes';
const options = {
"config": {
"activeEditor": "script",
"style": {
"language": "css",
"content": ".title {\n color: green;\n font-family: sans-serif;\n}\n"
},
"script": {
"language": "ripple",
"content": "import classes from './style.module.css';\n\nexport default component() {\n <h1 class={classes.title}>\n {'Hello, CSS Modules!'}\n </h1>\n}\n"
},
"processors": [
"cssmodules"
]
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"style": {
"language": "css",
"content": ".title {\n color: green;\n font-family: sans-serif;\n}\n"
},
"script": {
"language": "ripple",
"content": "import classes from './style.module.css';\n\nexport default component() {\n <h1 class={classes.title}>\n {'Hello, CSS Modules!'}\n </h1>\n}\n"
},
"processors": [
"cssmodules"
]
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"activeEditor": "script",
"style": {
"language": "css",
"content": ".title {\n color: green;\n font-family: sans-serif;\n}\n"
},
"script": {
"language": "ripple",
"content": "import classes from './style.module.css';\n\nexport default component() {\n <h1 class={classes.title}>\n {'Hello, CSS Modules!'}\n </h1>\n}\n"
},
"processors": [
"cssmodules"
]
}
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"activeEditor": "script",
"style": {
"language": "css",
"content": ".title {\n color: green;\n font-family: sans-serif;\n}\n"
},
"script": {
"language": "ripple",
"content": "import classes from './style.module.css';\n\nexport default component() {\n <h1 class={classes.title}>\n {'Hello, CSS Modules!'}\n </h1>\n}\n"
},
"processors": [
"cssmodules"
]
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
let options = $state({
"config": {
"activeEditor": "script",
"style": {
"language": "css",
"content": ".title {\n color: green;\n font-family: sans-serif;\n}\n"
},
"script": {
"language": "ripple",
"content": "import classes from './style.module.css';\n\nexport default component() {\n <h1 class={classes.title}>\n {'Hello, CSS Modules!'}\n </h1>\n}\n"
},
"processors": [
"cssmodules"
]
}
});
let container = $state(null);
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>
import { createPlayground, type EmbedOptions } from 'livecodes';
export default function App() {
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"style": {
"language": "css",
"content": ".title {\n color: green;\n font-family: sans-serif;\n}\n"
},
"script": {
"language": "ripple",
"content": "import classes from './style.module.css';\n\nexport default component() {\n <h1 class={classes.title}>\n {'Hello, CSS Modules!'}\n </h1>\n}\n"
},
"processors": [
"cssmodules"
]
}
};
const onMounted = (container: HTMLElement) => {
createPlayground(container, options);
};
return <div ref={onMounted}></div>;
}
import { createPlayground, type EmbedOptions } from 'livecodes';
export default component App() {
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"style": {
"language": "css",
"content": ".title {\n color: green;\n font-family: sans-serif;\n}\n"
},
"script": {
"language": "ripple",
"content": "import classes from './style.module.css';\n\nexport default component() {\n <h1 class={classes.title}>\n {'Hello, CSS Modules!'}\n </h1>\n}\n"
},
"processors": [
"cssmodules"
]
}
};
const onMount = (container: HTMLElement) => {
createPlayground(container, options);
};
<div {ref onMount}></div>
}
CSS Frameworks
CSS Frameworks supported in LiveCodes (e.g. Tailwind CSS, UnoCSS, WindiCSS) can detect class names added in Ripple components.
Make sure that the required utility is enabled (from style editor menu or in processors property of configuration object).
Example:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Ripple
import { createPlayground } from 'livecodes';
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n\n <div class=\"flex flex-col items-center justify-center min-h-screen bg-gray-100\">\n <h1 class=\"header\">{'Ripple + Tailwind CSS'}</h1>\n <div class=\"flex items-center space-x-4 mb-4\">\n <button\n onClick={() => @count--}\n class=\"w-12 px-4 py-2 bg-red-500 text-white rounded-lg shadow hover:bg-red-600 transition cursor-pointer\"\n >\n {'-'}\n </button>\n <span class=\"text-3xl font-semibold text-gray-800\">{@count}</span>\n <button\n onClick={() => @count++}\n class=\"w-12 px-4 py-2 bg-green-500 text-white rounded-lg shadow hover:bg-green-600 transition cursor-pointer\"\n >\n {'+'}\n </button>\n </div>\n <button\n onClick={() => @count = 0}\n class=\"px-6 py-2 bg-blue-500 text-white rounded-lg shadow hover:bg-blue-600 transition cursor-pointer\"\n >\n {'Reset'}\n </button>\n </div>\n\n <style>\n .header {\n @apply text-2xl font-bold text-gray-800 mb-4;\n }\n </style>\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n\n <div class=\"flex flex-col items-center justify-center min-h-screen bg-gray-100\">\n <h1 class=\"header\">{'Ripple + Tailwind CSS'}</h1>\n <div class=\"flex items-center space-x-4 mb-4\">\n <button\n onClick={() => @count--}\n class=\"w-12 px-4 py-2 bg-red-500 text-white rounded-lg shadow hover:bg-red-600 transition cursor-pointer\"\n >\n {'-'}\n </button>\n <span class=\"text-3xl font-semibold text-gray-800\">{@count}</span>\n <button\n onClick={() => @count++}\n class=\"w-12 px-4 py-2 bg-green-500 text-white rounded-lg shadow hover:bg-green-600 transition cursor-pointer\"\n >\n {'+'}\n </button>\n </div>\n <button\n onClick={() => @count = 0}\n class=\"px-6 py-2 bg-blue-500 text-white rounded-lg shadow hover:bg-blue-600 transition cursor-pointer\"\n >\n {'Reset'}\n </button>\n </div>\n\n <style>\n .header {\n @apply text-2xl font-bold text-gray-800 mb-4;\n }\n </style>\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n\n <div class=\"flex flex-col items-center justify-center min-h-screen bg-gray-100\">\n <h1 class=\"header\">{'Ripple + Tailwind CSS'}</h1>\n <div class=\"flex items-center space-x-4 mb-4\">\n <button\n onClick={() => @count--}\n class=\"w-12 px-4 py-2 bg-red-500 text-white rounded-lg shadow hover:bg-red-600 transition cursor-pointer\"\n >\n {'-'}\n </button>\n <span class=\"text-3xl font-semibold text-gray-800\">{@count}</span>\n <button\n onClick={() => @count++}\n class=\"w-12 px-4 py-2 bg-green-500 text-white rounded-lg shadow hover:bg-green-600 transition cursor-pointer\"\n >\n {'+'}\n </button>\n </div>\n <button\n onClick={() => @count = 0}\n class=\"px-6 py-2 bg-blue-500 text-white rounded-lg shadow hover:bg-blue-600 transition cursor-pointer\"\n >\n {'Reset'}\n </button>\n </div>\n\n <style>\n .header {\n @apply text-2xl font-bold text-gray-800 mb-4;\n }\n </style>\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n\n <div class=\"flex flex-col items-center justify-center min-h-screen bg-gray-100\">\n <h1 class=\"header\">{'Ripple + Tailwind CSS'}</h1>\n <div class=\"flex items-center space-x-4 mb-4\">\n <button\n onClick={() => @count--}\n class=\"w-12 px-4 py-2 bg-red-500 text-white rounded-lg shadow hover:bg-red-600 transition cursor-pointer\"\n >\n {'-'}\n </button>\n <span class=\"text-3xl font-semibold text-gray-800\">{@count}</span>\n <button\n onClick={() => @count++}\n class=\"w-12 px-4 py-2 bg-green-500 text-white rounded-lg shadow hover:bg-green-600 transition cursor-pointer\"\n >\n {'+'}\n </button>\n </div>\n <button\n onClick={() => @count = 0}\n class=\"px-6 py-2 bg-blue-500 text-white rounded-lg shadow hover:bg-blue-600 transition cursor-pointer\"\n >\n {'Reset'}\n </button>\n </div>\n\n <style>\n .header {\n @apply text-2xl font-bold text-gray-800 mb-4;\n }\n </style>\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
let options = $state({
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n\n <div class=\"flex flex-col items-center justify-center min-h-screen bg-gray-100\">\n <h1 class=\"header\">{'Ripple + Tailwind CSS'}</h1>\n <div class=\"flex items-center space-x-4 mb-4\">\n <button\n onClick={() => @count--}\n class=\"w-12 px-4 py-2 bg-red-500 text-white rounded-lg shadow hover:bg-red-600 transition cursor-pointer\"\n >\n {'-'}\n </button>\n <span class=\"text-3xl font-semibold text-gray-800\">{@count}</span>\n <button\n onClick={() => @count++}\n class=\"w-12 px-4 py-2 bg-green-500 text-white rounded-lg shadow hover:bg-green-600 transition cursor-pointer\"\n >\n {'+'}\n </button>\n </div>\n <button\n onClick={() => @count = 0}\n class=\"px-6 py-2 bg-blue-500 text-white rounded-lg shadow hover:bg-blue-600 transition cursor-pointer\"\n >\n {'Reset'}\n </button>\n </div>\n\n <style>\n .header {\n @apply text-2xl font-bold text-gray-800 mb-4;\n }\n </style>\n}\n"
},
"processors": [
"tailwindcss"
]
}
});
let container = $state(null);
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>
import { createPlayground, type EmbedOptions } from 'livecodes';
export default function App() {
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n\n <div class=\"flex flex-col items-center justify-center min-h-screen bg-gray-100\">\n <h1 class=\"header\">{'Ripple + Tailwind CSS'}</h1>\n <div class=\"flex items-center space-x-4 mb-4\">\n <button\n onClick={() => @count--}\n class=\"w-12 px-4 py-2 bg-red-500 text-white rounded-lg shadow hover:bg-red-600 transition cursor-pointer\"\n >\n {'-'}\n </button>\n <span class=\"text-3xl font-semibold text-gray-800\">{@count}</span>\n <button\n onClick={() => @count++}\n class=\"w-12 px-4 py-2 bg-green-500 text-white rounded-lg shadow hover:bg-green-600 transition cursor-pointer\"\n >\n {'+'}\n </button>\n </div>\n <button\n onClick={() => @count = 0}\n class=\"px-6 py-2 bg-blue-500 text-white rounded-lg shadow hover:bg-blue-600 transition cursor-pointer\"\n >\n {'Reset'}\n </button>\n </div>\n\n <style>\n .header {\n @apply text-2xl font-bold text-gray-800 mb-4;\n }\n </style>\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
const onMounted = (container: HTMLElement) => {
createPlayground(container, options);
};
return <div ref={onMounted}></div>;
}
import { createPlayground, type EmbedOptions } from 'livecodes';
export default component App() {
const options: EmbedOptions = {
"config": {
"activeEditor": "script",
"script": {
"language": "ripple",
"content": "import { track } from 'ripple';\n\nexport default component Counter() {\n let count = track(0);\n\n <div class=\"flex flex-col items-center justify-center min-h-screen bg-gray-100\">\n <h1 class=\"header\">{'Ripple + Tailwind CSS'}</h1>\n <div class=\"flex items-center space-x-4 mb-4\">\n <button\n onClick={() => @count--}\n class=\"w-12 px-4 py-2 bg-red-500 text-white rounded-lg shadow hover:bg-red-600 transition cursor-pointer\"\n >\n {'-'}\n </button>\n <span class=\"text-3xl font-semibold text-gray-800\">{@count}</span>\n <button\n onClick={() => @count++}\n class=\"w-12 px-4 py-2 bg-green-500 text-white rounded-lg shadow hover:bg-green-600 transition cursor-pointer\"\n >\n {'+'}\n </button>\n </div>\n <button\n onClick={() => @count = 0}\n class=\"px-6 py-2 bg-blue-500 text-white rounded-lg shadow hover:bg-blue-600 transition cursor-pointer\"\n >\n {'Reset'}\n </button>\n </div>\n\n <style>\n .header {\n @apply text-2xl font-bold text-gray-800 mb-4;\n }\n </style>\n}\n"
},
"processors": [
"tailwindcss"
]
}
};
const onMount = (container: HTMLElement) => {
createPlayground(container, options);
};
<div {ref onMount}></div>
}
Language Info
Name
ripple
Extensions
.ripple
Editor
script
Compiler
The official Ripple compiler.
Version
ripple: v0.2.15
Code Formatting
Using Prettier.
Custom Settings
Custom settings can be added to the property ripple.
These include:
"disableAutoRender"- disables auto-rendering (default:false)."version"- specifies the version of Ripple compiler (default:"latest").
Example:
{
"ripple": {
"version": "0.2.15"
}
}
Please note that custom settings should be valid JSON (i.e. functions are not allowed).
Starter Template
https://livecodes.io/?template=ripple
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Ripple
import { createPlayground } from 'livecodes';
const options = {
"template": "ripple"
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"template": "ripple"
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"template": "ripple"
};
return (<LiveCodes {...options}></LiveCodes>);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"template": "ripple"
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import { onMount } from 'svelte';
import { createPlayground } from 'livecodes';
let options = $state({
"template": "ripple"
});
let container = $state(null);
onMount(() => {
createPlayground(container, options);
});
</script>
<div bind:this="{container}"></div>
import { createPlayground, type EmbedOptions } from 'livecodes';
export default function App() {
const options: EmbedOptions = {
"template": "ripple"
};
const onMounted = (container: HTMLElement) => {
createPlayground(container, options);
};
return <div ref={onMounted}></div>;
}
import { createPlayground, type EmbedOptions } from 'livecodes';
export default component App() {
const options: EmbedOptions = {
"template": "ripple"
};
const onMount = (container: HTMLElement) => {
createPlayground(container, options);
};
<div {ref onMount}></div>
}