strandiox
v0.1.0
Published
A declarative UI DSL for building interactive apps in JavaScript — inspired by Gradio.
Downloads
32
Maintainers
Readme
🧵 strandio
strandio is a declarative UI DSL for JavaScript, inspired by Gradio.
Define your interface and handler logic in a single app.js file — strandio bundles everything into a fully client-side SPA. No server round-trips, no serialisation. Your handler functions run directly in the browser.
import { strandio } from "@strandio/core";
async function* generate(prompt) {
for (const token of await callLLM(prompt)) yield token;
}
const app = strandio((ui) => {
const prompt = ui.textbox({ placeholder: "Ask anything…" });
const chatbot = ui.chatbot();
ui.button("Send")
.stream(generate)
.from(prompt)
.to(chatbot);
});
export default app;node scripts/build.js # → packages/svelte/dist/ (open in any browser)How It Works
app.js (your handlers + UI layout)
↓ Vite bundles everything together
packages/svelte/dist/ (static HTML + JS + CSS)
↓ open in browser
User clicks button → handler() runs in browser → output updatesHandler functions are bundled directly into the browser JS by Vite. There are no HTTP round-trips for event execution. If a handler needs to call an external API (e.g. an LLM), it does so itself via fetch().
Monorepo Structure
strandio/
├── packages/
│ ├── core/ ← @strandio/core
│ │ ├── src/
│ │ │ ├── strandio.js ← strandio() entry point
│ │ │ ├── Builder.js ← builds the node tree
│ │ │ ├── ComponentHandle.js ← fluent event API (.click, .stream…)
│ │ │ ├── Event.js ← click / change / submit event
│ │ │ ├── StreamEvent.js ← async generator streaming event
│ │ │ ├── Node.js ← base UI node
│ │ │ ├── Block.js ← layout node
│ │ │ ├── State.js ← reactive state
│ │ │ ├── Registry.js ← component DSL factory
│ │ │ └── components/ ← component prop schemas (renderer-agnostic)
│ │ └── examples/
│ │ ├── basic.js ← simple adder form
│ │ └── chat.js ← streaming chat
│ │
│ └── svelte/ ← @strandio/svelte
│ ├── src/
│ │ ├── main.js ← imports app.js, mounts Svelte
│ │ ├── App.svelte
│ │ ├── NodeRenderer.svelte
│ │ ├── components/ ← Svelte UI components
│ │ └── runtime/
│ │ ├── EventRunner.js ← calls handlers directly in browser
│ │ └── store.js ← reactive value stores per node
│ └── vite.config.js
│
├── server/
│ └── index.js ← optional static file server (production)
├── scripts/
│ └── build.js ← orchestrates the Vite build
├── app.js ← your app entry point
└── package.json ← npm workspace rootInstallation
Requires Node ≥ 18.
Clone and install:
git clone https://github.com/your-org/strandio.git
cd strandio
npm installQuick Start
1. Write your app
Create or edit app.js at the repo root:
import { strandio } from "@strandio/core";
function add(a, b) {
return Number(a) + Number(b);
}
const app = strandio((ui) => {
const a = ui.number({ label: "A", value: 0 });
const b = ui.number({ label: "B", value: 0 });
const out = ui.number({ label: "Result", readonly: true });
ui.button("Add").click(add, [a, b], [out]);
});
export default app;2. Build
node scripts/build.js
# or: npm run build:appOutput lands in packages/svelte/dist/.
3. Open in browser
npx serve packages/svelte/dist
# → http://localhost:3000Building an Example
npm run example:basic # builds packages/core/examples/basic.js
npm run example:chat # builds packages/core/examples/chat.jsOr target any file directly:
node scripts/build.js path/to/myapp.jsEvents
Click / Change / Submit
btn.click(handler, [inputA, inputB], [output]);
input.change(handler, [input], [output]);
form.submit(handler, [input], [output]);Handlers are plain JavaScript functions. They receive input values and return output values:
function multiply(a, b) {
return Number(a) * Number(b); // single output
}
function split(text) {
return [text.toUpperCase(), text.length]; // multiple outputs
}Streaming (async generators)
btn.stream(asyncGenerator)
.from(input1, input2)
.to(output);The generator runs locally in the browser — each yielded value is applied to the output store immediately:
async function* generate(prompt) {
const res = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ model: "gpt-4o", stream: true, messages: [{ role: "user", content: prompt }] }),
});
for await (const chunk of parseSSE(res.body)) yield chunk;
}Component Library
Inputs
| Component | DSL call |
|-----------|----------|
| Text box | ui.textbox({ label, placeholder, lines }) |
| Number | ui.number({ label, value, minimum, maximum, step }) |
| Slider | ui.slider({ label, minimum, maximum, step }) |
| Dropdown | ui.dropdown({ label, choices, multiselect }) |
| Checkbox | ui.checkbox({ label, value }) |
| Radio | ui.radio({ label, choices }) |
| File upload | ui.file({ file_types, file_count }) |
| Image | ui.image({ type, sources }) |
| Audio | ui.audio() |
| Video | ui.video() |
Outputs
| Component | DSL call |
|-----------|----------|
| Markdown | ui.markdown({ value }) |
| HTML | ui.html() |
| JSON | ui.json() |
| Dataframe | ui.dataframe({ headers }) |
| Chatbot | ui.chatbot() |
| Code | ui.code({ language }) |
| Label | ui.label() |
| Progress | ui.progress() |
Interactive
| Component | DSL call |
|-----------|----------|
| Button | ui.button(label, { variant }) |
| Clear button | ui.clear_button() |
| Submit button | ui.submit_button() |
Layout
| Block | DSL call |
|-------|----------|
| Row | ui.row(() => { … }) |
| Column | ui.column(() => { … }, { scale }) |
| Tab | ui.tab(label, () => { … }) |
| Accordion | ui.accordion(() => { … }, { label }) |
| Group | ui.group(() => { … }) |
State
const app = strandio((ui, state) => {
state.set({ count: 0 });
const counter = ui.number({ label: "Count" });
counter.bind(state);
ui.button("Increment").click(() => {
state.set({ count: state.get().count + 1 });
});
});Scripts
| Command | What it does |
|---------|-------------|
| npm run build:app | Build app.js → packages/svelte/dist/ |
| npm run example:basic | Build the basic adder example |
| npm run example:chat | Build the streaming chat example |
| npm run build | Bundle framework → dist/ (Rollup, for npm distribution) |
| npm test | Run all tests (Vitest) |
Packages
| Package | Description |
|---------|-------------|
| @strandio/core | The DSL — strandio(), event model, component builder |
| @strandio/svelte | Svelte renderer — Vite-based SPA with client-side event execution |
License
MIT
