# JavaScript, React and Svelte BUI runs from JavaScript through a native addon built with napi-rs, much as [GPUIX](https://www.npmjs.com/package/@gpuix/react) drives GPUI. You get native windows drawn on the CPU or the GPU (Direct3D 11 / OpenGL), HTML elements, CSS and Tailwind classes, and the same engine as the Rust crate. There's no browser and no Electron. It works on **Node.js** and **Bun**, and `bui build` turns an app into a **single executable of about 7 MB**: BUI with an embedded QuickJS engine ([Standalone apps](#standalone-apps)). | Package | What it is | |---|---| | [`@blender-ui/native`](https://www.npmjs.com/package/@blender-ui/native) (`bindings/node`) | A DOM-like API (`createElement`, `setAttribute`, `appendChild`, `addEventListener`, `requestAnimationFrame`) and signals | | [`@blender-ui/react`](https://www.npmjs.com/package/@blender-ui/react) (`bindings/react`) | A React 19.3 renderer; React's hooks by React's names, backed by signals | | [`@blender-ui/svelte`](https://www.npmjs.com/package/@blender-ui/svelte) (`bindings/svelte`) | Svelte 5 components (runes, snippets, scoped styles) on the DOM-like API | **Platforms:** prebuilt for Windows x64. On Linux, build the addon from source (see [Building](#building)); windows are native X11 windows, on the CPU or with OpenGL. On macOS use `headless: true`: macOS only allows windows on the main thread, and BUI runs on a UI thread of its own in Node. A browser (WebAssembly) build is planned. ## Plain JavaScript ```js const { App, signal } = require('@blender-ui/native') const app = new App({ gpu: true }) const win = app.window({ title: 'Hello', width: 360, height: 220 }) win.body.setAttribute('styled', '') // Blender's look for the page (default: browser styling) win.body.className = 'm-0 p-6 flex flex-col gap-3 bg-[#2b2b2b] text-[#e6e6e6]' const clicks = signal(0) const label = win.createElement('span') label.textContent = clicks // bound: follows the signal const button = win.createElement('button') button.textContent = 'Add one' button.addEventListener('click', () => clicks.value++) win.body.appendChild(label) win.body.appendChild(button) ``` **Attributes** are the ones the Rust crate takes: - `class` / `className`, including Tailwind classes; - `style`, as a CSS string or an object such as `{ backgroundColor: 'red', width: 40 }`; - `id`, `href`, `value`, `src` and so on. Any of them can be a signal. **Events** are `click`, `mousedown`, `mouseup`, `mousemove`, `mouseenter`, `mouseleave`, `wheel`, `keydown`, `keyup`, `input` and `change`. - **Bubbling:** events bubble through the tree, except `mouseenter` and `mouseleave`, as in the DOM. `stopPropagation()` and `stopImmediatePropagation()` work. - **`event.target`** is the deepest element listening for that event type. - **`preventDefault()`** only marks the event: events reach JavaScript after BUI has handled them. **Frames and measurement:** `win.requestAnimationFrame(cb)` runs `cb` before the next frame, paced by the display, and what it changes renders in that frame. `el.getBoundingClientRect()` and `win.screenshot()` return promises. **Tests:** `new App({ headless: true })` renders off-screen, and `win.click(x, y)` simulates input. The tree behaves like the DOM: - **Moving a node:** `appendChild` or `insertBefore` moves a node that already has a parent. - **Errors:** inserting a node into itself, or before a node that isn't a child, throws. - **Freeing nodes:** `destroy()` frees a removed subtree natively. - **DOM methods:** `before`, `after`, `append`, `prepend`, `replaceWith`, `cloneNode`, `contains`, `dispatchEvent`, `el.style.setProperty`, `el.classList`, and DOM properties that reflect attributes (`disabled`, `hidden`, `value`, `checked`, `type` ...). Siblings are a linked list. - **`document`** makes detached nodes, which are created natively (with their subtree) when inserted into a window's tree; fragments and comments work as in the DOM, and a removed node made by `document` that isn't reinserted by the end of the tick is freed for you. ## React ```jsx import { render, useState } from '@blender-ui/react' function Counter () { const [count, setCount] = useState(0) return (
Clicks: {count}
) } render(, { title: 'BUI + React', width: 420, height: 220, gpu: true }) ``` Set `"jsxImportSource": "@blender-ui/react"` in `tsconfig.json` or in your bundler's JSX options. For code without JSX, use this package's `createElement`. The runtime does two things: - **Render tracking:** a component that reads a signal while rendering re-renders when it changes. - **TypeScript:** host-element props accept signals of their type. Host components are HTML elements, as in React DOM: - `className`, `style` objects, `htmlFor` and boolean attributes work. - `onChange` fires on every edit of a text field. On checkboxes, radios, selects and other controls, it fires when the value changes, even if the control's `type` changes later. - Event handlers run as discrete updates, so a click's state change commits immediately. ``, ``, transitions, actions and `` work; view transitions commit without animating. `createRoot(window)` renders into a window you opened yourself, and closing the window unmounts its root. ## Svelte ```svelte
Clicks: {count}
``` ```js // app.js: node --import @blender-ui/svelte/register app.js // (or bun --preload @blender-ui/svelte/register app.js, or npx bui build app.js) import { render } from '@blender-ui/svelte' import Counter from './Counter.svelte' render(Counter, { title: 'BUI + Svelte', width: 420, height: 220, gpu: true }) ``` `@blender-ui/svelte` mirrors `@blender-ui/react`: `render(Component, { props, context, ...windowOptions })`, `createRoot(window)`, `act(fn)` (Svelte's `flushSync`), Blender's widgets as components and BUI's signals, each of which is a Svelte store (`$count`). Svelte 5.57 or newer; Node.js 22.15 or newer. - **How it renders.** Svelte has no custom-renderer API: compiled components call the DOM. `@blender-ui/native/globals` provides it (`document`, `window`, the node classes), on the same retained tree React uses, so only each tick's changes cross to the UI thread. Components compile with `fragments: 'tree'` (templates from `createElement`, no HTML parser). - **`register`** compiles `.svelte` and `.svelte.js` files on import (cached in `node_modules/.cache/@blender-ui/svelte`) and resolves `svelte` to its browser build; `bui build` finds the same plugin by itself. - **Events:** Svelte delegates `onclick` and friends to the window's body; an element with a delegated handler listens natively, so BUI sends it its events, and Svelte bubbles them through `event.composedPath()`. - **Styles:** a component's scoped `