Widgets

Build your first widget

Walk-through: from minimal label to a richer tree with hover popover, in 5 minutes.

This page builds one widget end-to-end. By the end you'll have:

  • A live battery percentage in the bar
  • A red ⚠️ chip that appears only when battery is below 20%
  • A hover popover showing the full charge state, ETA, and a colour gauge

Open the widget folder

bash
mkdir -p "$HOME/Library/Application Support/ApexDock/widgets"
$EDITOR "$HOME/Library/Application Support/ApexDock/widgets/battery.yml"

Or use Settings → Integrations → YAML Widgets → Open and create battery.yml there.

Step 1: a label-only widget

yaml
id: battery
command: |
  pmset -g batt | awk '/InternalBattery/ { match($0, /[0-9]+%/); print substr($0, RSTART, RLENGTH); }'
interval: 30
symbol: battery.100
tooltip: "Battery"
order: 50

Save. Within ~2 seconds the bar shows a battery glyph with the current percentage. The script's first line of stdout becomes the label.

Step 2: switch to JSON output

The label-only mode is fine, but we need access to the percentage as a number for the gauge. Switch to JSON:

yaml
id: battery
command: |
  pmset -g batt | awk '
    /InternalBattery/ {
      match($0, /[0-9]+%/); pct = substr($0, RSTART, RLENGTH-1)
      match($0, /[0-9]+:[0-9]+/); rem = (RSTART > 0) ? substr($0, RSTART, RLENGTH) : ""
      charging = ($0 ~ /; charging/) ? "true" : "false"
      printf "{\"pct\":%s,\"charging\":%s,\"remaining\":\"%s\"}\n", pct, charging, rem
    }'
interval: 30
...

stdout is now a single JSON object. The runner parses it and exposes the keys to bindings.

Step 3: add a rich view

Replace the symbol/tooltip keys with a view: tree:

yaml
view:
  hstack:
    spacing: 4
    children:
      - symbol: { name: battery.100, color: green }
      - text:
          content: "${pct}%"
          size: 10
          weight: medium
          design: rounded
          monospacedDigit: true

Save. The bar now renders a green battery glyph + the percentage number, with smooth animation as the value changes.

Step 4: change colour when low

Wrap the symbol in an if:

yaml
view:
  hstack:
    spacing: 4
    children:
      - if:
          when: "${pct} < 20"
          then:
            symbol: { name: battery.0, color: "#FF3B30" }
          else:
            symbol: { name: battery.100, color: green }
      - text: { content: "${pct}%", size: 10, weight: medium, design: rounded, monospacedDigit: true }

The bar now flips to a red empty-battery glyph below 20%.

Step 5: add a hover popover

Add a hover: tree at the top level (sibling of view:):

yaml
hover:
  vstack:
    spacing: 8
    alignment: leading
    frame: { width: 200 }
    children:
      - hstack:
          children:
            - text: { content: "Battery", size: 11, color: secondary, weight: medium }
            - spacer: {}
            - text: { content: "${pct}%", size: 18, weight: semibold, design: rounded }
      - gauge:
          value: "${pct}"
          min: 0
          max: 100
          tint: green
          width: 176
          height: 6
      - if:
          when: "${charging} == true"
          then:
            text: { content: "Charging — ${remaining} until full", size: 11, color: secondary }
          else:
            text: { content: "${remaining} remaining", size: 11, color: secondary }

Hover the bar cell. A 200-pt-wide popover slides up with the full state.

Step 6: open System Settings on click

Add click: at the top level:

yaml
click:
  shell: "open 'x-apple.systempreferences:com.apple.preference.battery'"

Now clicking the cell opens System Settings → Battery.

You shipped a widget

Total time: ~5 minutes. The complete file is in Recipes → Battery. From here:

  • Read Schema for the full set of nodes and modifiers
  • Read Bindings for path resolution and format specs
  • Read Control flow for forEach (lists), if (branches), and showIf (visibility)
  • Read Recipes for full-featured examples — disk, weather, GitHub PRs

Debugging tips

  • Save and watch the bar. YAML reload is FSEvent-driven; you'll see changes within ~100ms.
  • Parse errors show inline. A bad YAML emits a red ⚠️ chip in the tray with the error in its tooltip. Click the chip to reopen the file.
  • Check os_log. log show --predicate 'subsystem == "apexdock.widgets"' --info --last 5m for the full log, including widget warnings.
  • Test the script standalone. Run your command value in a shell and check stdout is single-line JSON.