Build your own Spacedash widget

Make a custom widget for Spacedash from a widget.json and an index.html: options, live system and media data, text size, and iCUE widgets in Spacedash.

Updated 15 Nov 2026

If you can make a web page, you can make a Spacedash widget. A widget is an HTML page with a small widget.json beside it that names it and lists its options. Spacedash shows it on your dashboard like a built-in widget: it gets a settings panel, follows the user's look and text size, and can read live CPU, GPU, memory and media data. Writing and importing your own widgets is free.

The two files

Everything lives in one folder, which you can also zip:

my-widget/
├── widget.json     name, size, options
└── index.html      the widget, plain HTML/CSS/JS

Scripts, stylesheets and images go in the same folder, and your page refers to them with relative paths.

Getting it into Spacedash

Import

Two ways work. On the Widgets page, press Import widget… and choose the folder's zip. Or copy the folder into %LOCALAPPDATA%\EdgeOS\widgets\ yourself.

Add it to a layout

Your widget is now listed under Imported on the Widgets page, and in the picker of the layout editor. Once it's on a profile it appears everywhere that profile does: Desktop Mode, the iCUE widget, and the preview inside the app.

Describing the widget: widget.json

{
  "id": "hello-widget",            // folder name after import; letters, digits, - _ .
  "name": "Hello Widget",
  "description": "One line for the catalogue card.",
  "author": "You",
  "version": "1.0.0",
  "entry": "index.html",           // optional, default index.html
  "w": 3, "h": 1,                  // default size in grid units (desktop grid is 12 × 4)
  "options": [                     // optional; shown on the Widgets page and per placement
    { "key": "greeting", "label": "Greeting", "type": "text",   "default": "Hello, Edge" },
    { "key": "showCpu",  "label": "Show CPU", "type": "switch", "default": true },
    { "key": "unit",     "label": "Unit",     "type": "seg",    "default": "c", "values": [["c", "°C"], ["f", "°F"]] }
  ]
}

Three fields deserve a note.

id is used as the folder's name once imported, and your widget's saved data is tied to it, so keep it the same from version to version. Allowed characters are letters, digits, -, _ and ..

w and h set the size a new placement starts with, counted in grid cells. On the desktop the grid has 12 columns and 4 rows.

options builds the settings panel. Each entry appears on the Widgets page and again for every placement. There are three types: text gives a text field, switch gives an on/off toggle, and seg gives a row of choices taken from values, where each choice is written as [value, label].

The page: index.html

Your page runs inside an iframe that covers the tile. The iframe is sandboxed with allow-scripts allow-same-origin allow-forms allow-popups.

Spacedash serves every widget from its own web origin:

http://w-<hash>.localhost:57140/custom/<id>/

The <hash> part is derived from the id in your widget.json. Updating the widget therefore keeps the origin, and with it whatever you stored in localStorage.

Data Spacedash sends you

WhenHowPayload
On loadQuery string?mode=desktop or ?mode=widget, plus &options= and the options as JSON
On load, and when options changepostMessage{ type: "edgeos-options", options, mode }
Every second while visiblepostMessage{ type: "edgeos-state", state }

The state object is the one Spacedash's own widgets read:

{
  "edge":   { "found": true, "mode": "desktop", "bounds": { "x": 1920, "y": 0, "w": 2560, "h": 720 }, "icue": true },
  "system": { "cpu": 12, "gpu": 4, "gpuAvailable": true, "ramUsed": 11.2, "ramTotal": 31.1, "ramPct": 36 },
  "media":  { "title": "…", "artist": "…", "album": "…", "status": "playing|paused|stopped|none", "position": 42, "duration": 213, "thumbKey": "…" }
}

Listening for both messages takes a few lines:

window.addEventListener("message", e => {
  const m = e.data || {};
  if (m.type === "edgeos-options") applyOptions(m.options);
  if (m.type === "edgeos-state") draw(m.state);
});

The message names still begin with edgeos-. They are part of the widget API and didn't change with the app's name.

Making it feel built in

Let the tile show through. Give body a transparent background and the tile's own colour and rounded corners appear behind your content. Painting your own background is also fine.

Use em or rem for text. Two sliders control text size for the user: Appearance → Text size for everything, and the widget's Text size under Widgets → Customize. Spacedash multiplies them and writes the result as the font-size of your <html> element, and again as --scale on that element. Text sized in em or rem responds to both. Text sized in fixed px doesn't.

Design for fingers. The Edge is a touchscreen. Make anything tappable at least 44 px, don't hide controls behind hover, and assume there is no keyboard.

Don't count on a size. In Widget Mode your widget may be 1 × 1; on the desktop the same widget may be 4 × 2. Container units such as cqmin and percentages hold up where fixed pixels don't.

Network calls are yours to make. Spacedash doesn't proxy your requests. For anything that needs the operating system, like starting an app or reading a file, use a helper process next to the widget. Launchify and Deckord are built that way.

Running iCUE widgets

An .icuewidget file is imported exactly like your own widget. After unpacking it, Spacedash serves its index.html with an added script at the very start of <head>, which stands in for iCUE:

  • it defines window.iCUE = { isPreview: false };
  • for each x-icue-property meta tag it declares a global let with the value chosen in Spacedash, falling back to the tag's data-default;
  • it declares let icueEvents, calls icueEvents.onICUEInitialized() after the page loads, and calls icueEvents.onDataUpdated() each time a value changes. Settings take effect live; the widget is never reloaded for a change.

All of the widget's properties are listed under Customize → Options, arranged in the panels its x-icue-groups define, and sliders show the unit given in data-unit-label.

A few properties are treated specially:

iCUE featureIn Spacedash
Screen colours (accentColor, backgroundColor, textColor, transparency)iCUE reserves these four names. Spacedash fills them from the tile's look under Appearance: its accent, text colour and background. If the tile has a background of its own, the widget is made see-through over it (transparency 90).
Text sizeAppearance → Text size and the global Text size are multiplied into the widget's textScale slider, or a slider whose name ends in TextScale. A widget with no such slider is zoomed instead.
followIcueColorsWhen a widget has this switch, Spacedash turns it on for as long as the tile sets a colour.
Links (widgetbuilder.linkprovider)Shimmed. window.plugins.Linkprovider.open(url) makes Spacedash open an http(s) link in the default browser. No other entry in required_plugins has a shim yet, so a widget reports those features as missing.

Installing from a catalogue

Spacedash is able to install widgets listed in a catalogue, a JSON file published by Qaem:

{
  "widgets": [
    { "id": "hello-widget", "name": "Hello Widget", "description": "…", "author": "QaemDev", "version": "1.0.0",
      "kind": "custom",                       // or "icue"
      "download": "https://…/hello-widget.zip",
      "homepage": "https://…" }
  ]
}

An install fetches the widget's zip over https and imports it. Catalogue installs are a Premium feature, while importing files you have yourself stays free. The page for browsing the catalogue hasn't shipped yet; until it does, Import widget… is the only button on the Widgets page.