English | 简体中文
This guide is intended for developers who wish to write desktop widgets using JavaScript. widget-rs integrates the official GPUI Shell extension system from the gpui-kit ecosystem, allowing you to build declarative desktop widgets with lightweight JavaScript scripts (ES Modules + native QuickJS JIT environment) without needing to configure a Rust build environment.
- Native GPU Rendering, Zero DOM Overhead: Unlike Electron or WebView solutions, GPUI Shell's JavaScript engine runs in an embedded, high-performance QuickJS JIT virtual machine. Scripts directly map declarative
Viewclasses and node hierarchies into GPUI GPU primitives. - Full JavaScript Capabilities: Supports ES Modules, object-oriented class inheritance, computed properties, loops, conditional branches, event listeners (
on_click), and native timers (cx.timer.every). - Inherits Core Desktop Capabilities: Automatically inherits
widget-rs's Win32 click-through,Win+Ddesktop pinning, frosted glass acrylic transparency, always-on-top modes, and magnetic dragging.
Each widget extension is a self-contained directory placed inside the extensions root:
extensions/
└── my_widget/
├── manifest.json # Extension metadata and window options (Required)
├── main.js # Business logic and View rendering entry (Required)
├── jsconfig.json # Editor IDE autocompletion & type checking (Recommended)
├── gpui-kit.d.ts # gpui-kit global TypeScript definition (Recommended)
└── icon.png # Extension icon (Optional, .png or .svg)
Defines how the widget appears in the dashboard, its entry script, and window dimensions:
{
"id": "my_custom_widget",
"name": "My Custom Widget",
"version": "1.0.0",
"author": "Your Name",
"description": "Desktop widget built with GPUI Shell",
"entry": "main.js",
"icon": "clock",
"window": {
"width": 310.0,
"height": 92.0,
"transparent": true,
"blurred": true
}
}Note:
- Entry field supports
"entry": "main.js"or"main": "main.js".- Setting
"blurred": trueactivates Windows DirectComposition acrylic blur.
An extension exports a default class inheriting from gpui-kit's View.
init(props, cx): Lifecycle hook called on creation to set initial state, register timers, or set up listeners.render(cx): Returns a declarative native element tree built usingh_flex(),v_flex(),div(), etc.cx.notify(): Triggers GPUI differential re-rendering when reactive state changes.cx.timer.every(interval_ms, callback): Schedules recurring timer tasks.
import { View, div } from "gpui-kit";
import { h_flex, v_flex } from "gpui-base";
export default class DigitalClock extends View {
init(_props, cx) {
this.hoursMinutes = "--:--";
this.seconds = "00s";
this.date = "--/--";
this.updateTime();
// Update time every second and trigger a re-render
this.timer = cx.timer.every(1000, () => {
this.updateTime();
cx.notify();
});
}
updateTime() {
const now = new Date();
const pad = (n) => String(n).padStart(2, "0");
this.hoursMinutes = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
this.seconds = `${pad(now.getSeconds())}s`;
const weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
this.date = `${pad(now.getMonth() + 1)}/${pad(now.getDate())} ${weekdays[now.getDay()]}`;
}
render(_cx) {
return h_flex()
.w_full()
.h_full()
.px(16)
.py(12)
.gap(16)
.items_center()
.justify_between()
.bg("#0f172a65")
.rounded(16)
.border(1)
.border_color("#ffffff18")
.child(
div()
.text_size(38)
.font_bold()
.text_color("#f8fafc")
.child(this.hoursMinutes)
)
.child(
v_flex()
.gap(6)
.justify_center()
.child(
h_flex()
.px(6)
.py(2)
.rounded(6)
.bg("#00d99222")
.border(1)
.border_color("#00d99244")
.child(
div()
.text_size(11)
.font_bold()
.text_color("#00d992")
.child(this.seconds)
)
)
.child(
div()
.text_size(11)
.text_color("#94a3b8")
.child(this.date)
)
);
}
}GPUI Shell aligns closely with native GPUI fluent builder methods:
.w_full()/.h_full()/.size_full(): Fill container bounds.flex_1(): Expand to fill available flex space.px(val)/.py(val)/.p(val): Padding.gap(val): Flex item gap.items_center()/.justify_center()/.justify_between(): Flex alignment
.bg(hex_or_token): Background color (supports hex with alpha, e.g."#0f172a65").rounded(radius): Corner border radius in pixels.border(width): Border thickness.border_color(hex): Border color
.text_size(size): Font size in pixels.font_bold()/.font_normal()/.font_weight(weight): Font weight.text_color(color): Text color
widget-rs automatically discovers extensions from:
- User Directory:
%APPDATA%/tierlabx/widget-rs/extensions/ - Local Directory:
./extensions/relative to the executable
- Open the Dashboard and select the Widgets catalog.
- Click the JS Extensions filter tab at the top to display all dynamic JavaScript widgets.
- Click Open Extension Folder to jump directly to the extensions directory in File Explorer.
- After editing
main.js, click Refresh Extensions to instantly hot-reload changes without restarting the app.