Widgets

Widgets overview

What a widget is, the three transports, and which to pick.

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:

TransportWhen to useSchema
Unix socketLive 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 dropStatic widgets that don't change. Drop a *.json file and it appears. Edit and it updates.Same JSON wire.
YAML widgetsAnything 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:

yaml
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:

yaml
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 flowforEach, if, showIf
  • Recipes — battery, disk, weather, GitHub PRs

Locations

YAML widgets opt into bar zones via location:. Defaults to tray.

locationWhere it renders
tray (default)Right-side tray, before the built-in icons
pinnedInline with pinned apps, right of the last pin
unpinnedInline with running apps, right of the last running app
agentAgent zone, before the agent tile row
leadingRight after the start button
trailingVery right edge of the bar

Unknown values render nowhere — typo-safe but invisible.