A widget is a live cell ApexDock renders somewhere on the bar. It can be a single SF Symbol with an 8-character chip, or a full SwiftUI tree with a hover popover, sparklines, gauges, conditional rendering, and clickable rows. Whatever you can express in YAML.
Three transports
ApexDock exposes three ways to publish widgets — same JSON shape over the first two, plus a richer YAML schema for the third:
| Transport | When to use | Schema |
|---|---|---|
| Unix socket | Live updates from a long-running publisher (CLI agent, daemon). Per-connection ownership — closing the socket drops everything you pushed. | JSON wire (single icon + label). See API → Widget wire. |
| File drop | Static widgets that don't change. Drop a *.json file and it appears. Edit and it updates. | Same JSON wire. |
| YAML widgets | Anything that polls a shell command. Drop one .yaml/.yml file per widget into the widget folder. Hot-reloaded on save. Supports the full declarative tree (popovers, forEach, if). | YAML — see Schema. |
For most use cases, YAML is the right choice — it covers the polling pattern, supports rich rendering, and ships with a hot-reloader. The socket and file-drop transports stay around for live publishers that don't fit a polling model.
A minimal widget
Save this as ~/Library/Application Support/ApexDock/widgets/clock.yml:
id: clock
command: "date +%H:%M"
interval: 30
symbol: clock
tooltip: "System clock"
Save. The bar picks up the file via FSEvents and starts ticking. No relaunch.
A rich widget
Save this as a second file, ~/Library/Application Support/ApexDock/widgets/cpu-pulse.yml. It replaces the command output's label with a SwiftUI tree — sparkline, percentage, hover popover with circular gauge + top processes:
id: cpu-pulse
interval: 2
history:
pct: 60
command: |
PCT=$(top -l 1 -n 0 | awk '/CPU usage/ {gsub(/%/,"",$3); printf "%.0f", $3}')
printf '{"pct":%s}\n' "$PCT"
view:
hstack:
spacing: 5
children:
- symbol: { name: cpu, color: "#FF8800" }
- sparkline: { values: "${history.pct}", min: 0, max: 100, tint: "#FF8800", width: 40, height: 16 }
- text: { content: "${pct}%", design: monospaced }
hover:
vstack:
spacing: 12
frame: { width: 240 }
children:
- text: { content: "CPU", color: secondary }
- text: { content: "${pct}%", size: 30, weight: semibold }
- gauge: { value: "${pct}", min: 0, max: 100, tint: "#FF8800", style: circular, width: 44, height: 44 }
The script outputs JSON ({"pct": 42}), the YAML tree binds ${pct} and ${history.pct} (the runner maintains a 60-tick ring buffer for any field you list under history:).
What's in this section
- Getting started — your first rich widget, end-to-end
- Schema — every node, modifier, and primitive
- Bindings — how
${path}resolves, format specs, history - Control flow —
forEach,if,showIf - Recipes — battery, disk, weather, GitHub PRs
Locations
YAML widgets opt into bar zones via location:. Defaults to tray.
location | Where it renders |
|---|---|
tray (default) | Right-side tray, before the built-in icons |
pinned | Inline with pinned apps, right of the last pin |
unpinned | Inline with running apps, right of the last running app |
agent | Agent zone, before the agent tile row |
leading | Right after the start button |
trailing | Very right edge of the bar |
Unknown values render nowhere — typo-safe but invisible.