diff --git a/examples/js/.gitignore b/examples/js/.gitignore
new file mode 100644
index 0000000..b0d983b
--- /dev/null
+++ b/examples/js/.gitignore
@@ -0,0 +1,3 @@
+@girs/
+tsconfig.json
+env.d.ts
diff --git a/examples/js/simple-bar/.gitignore b/examples/js/simple-bar/.gitignore
new file mode 100644
index 0000000..6850183
--- /dev/null
+++ b/examples/js/simple-bar/.gitignore
@@ -0,0 +1,2 @@
+@girs/
+node_modules/
\ No newline at end of file
diff --git a/examples/js/simple-bar/README.md b/examples/js/simple-bar/README.md
new file mode 100644
index 0000000..a0d7bcb
--- /dev/null
+++ b/examples/js/simple-bar/README.md
@@ -0,0 +1,9 @@
+# Simple Bar Example
+
+A simple bar for Hyprland using
+
+- [Audio library](https://aylur.github.io/astal/libraries/audio).
+- [Battery library](https://aylur.github.io/astal/libraries/battery).
+- [Hyprland library](https://aylur.github.io/astal/libraries/hyprland).
+- [Mpris library](https://aylur.github.io/astal/libraries/mpris).
+- [Network library](https://aylur.github.io/astal/libraries/network).
diff --git a/examples/js/simple-bar/app.ts b/examples/js/simple-bar/app.ts
new file mode 100644
index 0000000..05f043a
--- /dev/null
+++ b/examples/js/simple-bar/app.ts
@@ -0,0 +1,8 @@
+import { App } from "astal"
+import style from "./style.scss"
+import Bar from "./widget/Bar"
+
+App.start({
+ css: style,
+ main: () => App.get_monitors().map(Bar),
+})
diff --git a/examples/js/simple-bar/style.scss b/examples/js/simple-bar/style.scss
new file mode 100644
index 0000000..f98286e
--- /dev/null
+++ b/examples/js/simple-bar/style.scss
@@ -0,0 +1,88 @@
+$bg: #212223;
+$fg: #f1f1f1;
+$accent: #378DF7;
+$radius: 7px;
+
+window.Bar {
+ border: none;
+ box-shadow: none;
+ background-color: $bg;
+ color: $fg;
+ font-size: 1.1em;
+ font-weight: bold;
+
+ button {
+ all: unset;
+ background-color: transparent;
+
+ &:hover label {
+ background-color: transparentize($fg, 0.84);
+ border-color: transparentize($accent, 0.8);
+ }
+
+ &:active label {
+ background-color: transparentize($fg, 0.8)
+ }
+ }
+
+ label {
+ transition: 200ms;
+ padding: 0 8px;
+ margin: 2px;
+ border-radius: $radius;
+ border: 1pt solid transparent;
+ }
+
+ .Workspaces .focused label {
+ color: $accent;
+ border-color: $accent;
+ }
+
+ .FocusedClient {
+ color: $accent;
+ }
+
+ .Media .Cover {
+ min-height: 1.2em;
+ min-width: 1.2em;
+ border-radius: $radius;
+ background-position: center;
+ background-size: contain;
+ }
+
+ .Battery label {
+ padding-left: 0;
+ margin-left: 0;
+ }
+
+ .AudioSlider {
+ * {
+ all: unset;
+ }
+
+ icon {
+ margin-right: .6em;
+ }
+
+ margin: 0 1em;
+
+ trough {
+ background-color: transparentize($fg, 0.8);
+ border-radius: $radius;
+ }
+
+ highlight {
+ background-color: $accent;
+ min-height: .8em;
+ border-radius: $radius;
+ }
+
+ slider {
+ background-color: $fg;
+ border-radius: $radius;
+ min-height: 1em;
+ min-width: 1em;
+ margin: -.2em;
+ }
+ }
+}
diff --git a/examples/js/simple-bar/widget/Bar.tsx b/examples/js/simple-bar/widget/Bar.tsx
new file mode 100644
index 0000000..2b25258
--- /dev/null
+++ b/examples/js/simple-bar/widget/Bar.tsx
@@ -0,0 +1,130 @@
+import { Variable, Astal, Gtk, Gdk, GLib, bind } from "astal"
+import Hyprland from "gi://AstalHyprland"
+import Mpris from "gi://AstalMpris"
+import Battery from "gi://AstalBattery"
+import Wp from "gi://AstalWp"
+import Network from "gi://AstalNetwork"
+
+function Wifi() {
+ const { wifi } = Network.get_default()
+
+ return
+}
+
+function AudioSlider() {
+ const speaker = Wp.get_default_wp()?.audio.defaultSpeaker!
+
+ return
+
+ speaker.volume = value}
+ value={bind(speaker, "volume")}
+ />
+
+}
+
+function BatteryLevel() {
+ const bat = Battery.get_default()
+
+ return
+
+
+}
+
+function Media() {
+ const player = Mpris.Player.new("spotify")
+
+ return
+
+ `background-image: url('${cover}');`
+ )}
+ />
+
+}
+
+function Workspaces() {
+ const hypr = Hyprland.get_default()
+
+ return
+ {bind(hypr, "workspaces").as(wss => wss
+ .sort((a, b) => a.id - b.id)
+ .map(ws => (
+
+ ))
+ )}
+
+}
+
+function FocusedClient() {
+ const hypr = Hyprland.get_default()
+ const focused = bind(hypr, "focusedClient")
+
+ return
+ {focused.as(client => (
+ client &&
+ ))}
+
+}
+
+function Time({ format = "%H:%M - %A %e." }) {
+ const time = Variable("").poll(1000, () =>
+ GLib.DateTime.new_now_local().format(format)!)
+
+ return