Skip to content

Your first widget

A widget is a folder with a widget.json and a web page. This walks through creating one, changing it and looking at the result. You need Umbra installed and a text editor; no build tools.

Terminal window
umbractl new my-widget

This writes a working widget into %APPDATA%\Umbra\widgets\my-widget and shows it on your desktop straight away. (Use --name "My widget" for a display name, or --dir <folder> to scaffold somewhere else; widgets outside the community folder aren’t picked up until you load them with umbractl create <folder>.)

You now have:

my-widget/
widget.json the manifest
index.html the page
style.css styles
main.js the code
{
"$schema": "https://raw.githubusercontent.com/AmineChr54/umbra/main/schemas/widget.schema.json",
"id": "my-widget",
"name": "my-widget",
"description": "Describe in one sentence why people want this widget.",
"author": "your-github-name",
"version": "0.1.0",
"entry": "index.html",
"width": 260,
"height": 120,
"permissions": { "hosts": [] },
"options": {
"greeting": { "label": "Greeting", "type": "text", "default": "Hello" }
}
}

id must equal the folder name. width and height are the widget’s size in logical pixels. permissions.hosts is empty, so this widget can’t reach the internet. options becomes a settings form on the widget’s card in My widgets. The manifest reference covers every field.

index.html loads Umbra’s stylesheet and SDK from the virtual host umbra.local, then your files:

<link rel="stylesheet" href="https://umbra.local/shared/umbra.css">
<link rel="stylesheet" href="style.css">
...
<script src="https://umbra.local/shared/umbra.js"></script>
<script src="main.js"></script>

umbra.css gives you design tokens (colors, fonts, radii), and umbra.js gives you the global umbra object: options, refresh, data fetching, saved data and more. See Page and lifecycle.

Edit main.js or style.css, then tell Umbra to re-scan:

Terminal window
umbractl reload

Change the option live without editing any file:

Terminal window
umbractl set my-widget greeting "Good morning"

To call an API, declare its host in widget.json, then use umbra.fetchJson inside umbra.every:

"permissions": { "hosts": ["api.open-meteo.com"] }
umbra.every(async () => {
const data = await umbra.fetchJson("https://api.open-meteo.com/v1/forecast?latitude=52.52&longitude=13.41&current=temperature_2m");
document.getElementById("app").textContent = `${data.current.temperature_2m}°`;
umbra.rendered();
});

Requests to any host you didn’t declare are blocked, and fetchJson rejects. See the JavaScript API.

Terminal window
umbractl capture my-widget shot.png

This saves a PNG of just that widget and nothing else on screen. Check it at other sizes with umbractl configure my-widget --scale 2. Testing with capture has the full loop.

Follow the design guidelines (true black background, tokens instead of hard-coded colors, tabular numbers), add a preview.png, and publish it by pull request so it shows up in everyone’s Browse page.