RAGS (raf's AGS) is a fork of Aylur's GTK Shell (AGS) v1, maintained to keep the v1 API stable while focusing on performance, security, and packaging.
RAGS is a library built for GJS that lets you define GTK 3 widgets declaratively with reactive property binding. It also provides services and utilities for interacting with the system so your widgets can respond to real-time state changes. GJS is the JavaScript runtime built on SpiderMonkey and GNOME platform libraries --- the same runtime GNOME Shell uses.
This site is split into two specific sections, with differing purposes:
Hand-written documentation covering concepts, configuration, and usage. Start here if you are new to RAGS.
Auto-generated from JSDoc annotations in the source code, listed under Modules in the sidebar. The main areas are:
Box,
Button, Label, Window, Slider, Stack, and 27 more).Audio,
Battery, Network, Hyprland, Mpris, etc.).Variable, Binding, Service base
class, and the App singleton.Use the sidebar to navigate between sections, or the search bar to find specific classes, functions, or types.
const time = Variable("", {
poll: [1000, function () {
return Date().toString();
}],
});
const Bar = (monitor) =>
Widget.Window({
monitor,
name: `bar${monitor}`,
anchor: ["top", "left", "right"],
exclusivity: "exclusive",
child: Widget.CenterBox({
start_widget: Widget.Label({
hpack: "center",
label: "Welcome to RAGS!",
}),
end_widget: Widget.Label({
hpack: "center",
label: time.bind(),
}),
}),
});
App.config({
windows: [Bar(0)],
});
Widgets are GTK 3 widget classes extended with reactive capabilities. Every
widget supports property binding, CSS styling, event hooks, and keyboard
shortcuts through the common Widget mixin.
All widgets accept a setup callback invoked after construction, and support
.hook(), .bind(), .on(), .poll(), and .keybind() methods for reactive
composition.
// Create widgets using factory functions
const myBox = Widget.Box({
vertical: true,
css: "padding: 12px;",
children: [
Widget.Label({ label: "Hello" }),
Widget.Button({
child: Widget.Label({ label: "Click me" }),
on_clicked: (self) => print("clicked!"),
}),
],
});
// Bind reactive data to widget properties
const myLabel = Widget.Label({
label: someVariable.bind(),
});
// Use CSS class toggling
const myButton = Widget.Button({
setup: (self) => {
self.toggleClassName("active", true);
},
});
Variable is the core reactive primitive. It holds a value and notifies
listeners when it changes.
// Simple variable
const count = Variable(0);
count.value++;
// Poll a command every 5 seconds
const cpu = Variable("", {
poll: [5000, "top -bn1 | grep Cpu"],
});
// Listen to a subprocess output stream
const workspaces = Variable([], {
listen: ["hyprctl workspaces -j", (out) => JSON.parse(out)],
});
// Bind to a widget property
Widget.Label({ label: count.bind().as((v) => `Count: ${v}`) });
Bindings connect reactive sources (services, variables) to widget properties. They transform values through a functional pipeline.
// Bind a service property
Widget.Label({
label: Audio.speaker.bind("volume").as(
(v) => `Volume: ${Math.round(v * 100)}%`,
),
});
// Merge multiple bindings
const label = Utils.merge(
[Battery.bind("percent"), Battery.bind("charging")],
(percent, charging) => `${percent}%${charging ? " (charging)" : ""}`,
);
Services are singleton GObject subclasses that expose system state via D-Bus or other backends. They emit signals and notify on property changes.
// Audio service
const volume = Audio.speaker?.volume ?? 0;
Audio.speaker?.connect("changed", () => {
print(`Volume: ${Audio.speaker.volume}`);
});
// Battery service
Widget.Label({
label: Battery.bind("percent").as((p) => `${p}%`),
});
// Network service
const ssid = Network.wifi?.ssid;
const strength = Network.wifi?.strength;
// Hyprland IPC
Hyprland.active.workspace.bind("id");
// MPRIS media players
const player = Mpris.players[0];
player?.playPause();
// Run shell commands
const output = Utils.exec("whoami");
const asyncOutput = await Utils.execAsync("ls -la");
// Spawn a long-running subprocess
Utils.subprocess(
["tail", "-f", "/tmp/some.log"],
(line) => print(line),
);
// File I/O
const content = Utils.readFile("/etc/hostname");
await Utils.writeFile("hello", "/tmp/test.txt");
Utils.monitorFile("/tmp/test.txt", (file, event) => {
print(`File changed: ${event}`);
});
// Timers
Utils.timeout(1000, () => print("1 second later"));
Utils.interval(5000, () => print("every 5 seconds"));
// Desktop notifications
Utils.notify({
summary: "Hello",
body: "This is a notification",
iconName: "dialog-information",
});
Windows are positioned using the Wayland Layer Shell protocol.
Widget.Window({
name: "my-bar",
anchor: ["top", "left", "right"],
exclusivity: "exclusive",
layer: "top",
monitor: 0,
margins: [0, 0, 0, 0],
keymode: "on-demand",
child: Widget.Box({/* ... */}),
});
The App singleton manages windows, CSS, and the application lifecycle.
App.config({
style: "./style.css",
windows: [Bar(0), Notifications()],
});
// Dynamically manage windows
App.toggleWindow("my-popup");
App.openWindow("my-popup");
App.closeWindow("my-popup");
// Hot-reload CSS
App.resetCss();
App.applyCss("./style.css");