Original article in Brazilian Portuguease: here
JavaScript started out as a technology for web UIs. Over time, Apple and Google helped expand it into other layers of the stack. Like it or not, you can build desktop apps, mobile apps, and HTTP servers with the language. A lot of that was made possible by the V8 interpreter and structural improvements to a language that was born poor and poorly structured — but over time became a solid option even for people who prefer something more verbose and strict, since they can use it with TypeScript.
Still, JavaScript GUI solutions are largely stuck to web UIs, so a big share of desktop and mobile apps today are just a browser dressed up as a window. Like any technology, that comes with trade-offs. These stacks are often less performant, ship bulkier installers, and don't run well on low-end machines such as Raspberry Pis or laptops with little RAM (something people care about a lot these days). I built a simple library to render screens with a different approach to tackle that problem.
A few years ago I came across a C library for games and graphical apps that I found really interesting — the one that has impressed me most in recent years. That library is Raylib. Released in November 2013 by Ramon Santamaria (raysan5), raylib is open source and lightweight: windowing, 2D/3D rendering via OpenGL, textures, fonts, audio, input, and multiplatform support (desktop, web, Android, Raspberry Pi, and more), with a simple API aimed at people getting started with games/graphics. On top of raylib came RayGui, another C library for building GUIs that render straight through the GPU with OpenGL, using Raylib.
My idea was this: wrap RayGui and some Raylib routines in JavaScript on the Node.js runtime, at least for now. When Android support lands, for example, another runtime will be used. Beyond calling RayGui from JavaScript, also provide data structures commonly used in frontends for building screens.
The language binding and the more repetitive work were done with vibecoding. The rest of the libraries and examples were written by hand and/or with targeted prompts. The LLM used was Grok 4.5.
The solution turned into something like a "React Native for Desktop." The library is called DebloatUi. It's available in this repository: https://github.com/misabitencourt/debloat-ui.
GUIs can be built in immediate mode — that is, since the whole UI is an infinite loop, screens can be written at that loop level. Working at a higher level with components and reactive programming is also possible. The ./examples directory includes the following examples:
-
basic.js— basic immediate mode (per-frame callback) -
counter.js— state, buttons, slider, textbox, and checkbox in low-level mode -
reactive-app.js— reactive app withapp/view/dispatch -
reactive-components.js— function components (React-like pattern) -
reactive-layout.js— grid/layout helpers -
reactive-scroll.js— panel with vertical scroll -
reactive-image.js— textures/images -
reactive-sprite.js— sprites / atlas regions -
reactive-multiline.js— multiline text -
reactive-table.js— table -
reactive-icons.js/reactive-icon-grid.js— icons and icon grid -
reactive-theme.js— raygui themes/styles -
reactive-keys.js/reactive-mouse.js/reactive-drag-text.js— keyboard, mouse, and drag -
game.js— game sample -
ts/layout.ts— layout in TypeScript
Here's a code sample — see how simple it is:
const {
app,
h,
label,
button,
panel,
textBlock,
statusBar,
} = require('debloatui/reactive');
// Component: panel + text + three buttons
function ActionCard({ x, y, w, height, title, text, onPrimary, onSecondary, onTertiary }) {
const pad = 14;
const btnW = Math.floor((w - pad * 2 - 8 * 2) / 3);
const btnH = 30;
const btnY = y + height - pad - btnH;
return h(
panel({ x, y, w, h: height, text: title }),
textBlock({
x: x + pad,
y: y + 36,
w: w - pad * 2,
h: height - 36 - btnH - pad * 2,
wrap: 'word',
text,
}),
button({ x: x + pad, y: btnY, w: btnW, h: btnH, text: 'OK', onclick: onPrimary }),
button({ x: x + pad + btnW + 8, y: btnY, w: btnW, h: btnH, text: 'Info', onclick: onSecondary }),
button({ x: x + pad + (btnW + 8) * 2, y: btnY, w: btnW, h: btnH, text: 'Close', onclick: onTertiary })
);
}
const setLast = (message) => (s) => ({ ...s, lastAction: message });
app({
init: { lastAction: 'Click a button on any card…' },
window: { title: 'debloatui — component cards', width: 920, height: 420, fps: 60 },
view: (s) =>
h(
label({ x: 24, y: 16, w: 872, h: 24, text: 'Componentization · function components' }),
label({ x: 24, y: 40, w: 872, h: 22, text: `Last action: ${s.lastAction}` }),
ActionCard({
x: 24,
y: 72,
w: 280,
height: 260,
title: 'Backups',
text: 'Create a local backup of your project files.',
onPrimary: setLast('Backups · OK'),
onSecondary: setLast('Backups · Info'),
onTertiary: setLast('Backups · Close'),
}),
statusBar({
x: 0,
y: 400,
w: 920,
h: 20,
text: 'ActionCard({ text, onPrimary, ... }) · examples/reactive-components.js',
})
),
});


Top comments (0)