# 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 `