Skip to content

Repository files navigation

StatusBar

image
output_small.mp4

A Swift-native custom status bar for macOS, inspired by sketchybar.

CI Release Homebrew Swift 6.2 macOS 26+ License: MIT


Requirements

  • macOS 26 or later
  • Xcode 26 or later (download) — only for building from source

Installation

Homebrew (recommended)

brew tap hytfjwr/tap
brew install --cask statusbar

Build from Source

git clone https://github.com/hytfjwr/StatusBar.git
cd StatusBar
make run-dev   # Debug build
make run-app   # Release build

Built-in Widgets

Widget Description Update
Apple Menu System actions & preferences Event
Front App Currently focused application Event
CPU Graph Real-time CPU usage mini-graph, per-core & top-process popup 2s
Memory Graph RAM usage mini-graph, memory breakdown & top-process popup 2s
Network Upload / download speeds, SSID / IP / per-interface popup 2s
Battery Charge level & charging state 60s
Volume Volume level with popup control Event
Bluetooth Connected device count, AirPods L/R/Case battery in popup 10s
Disk Usage Disk utilization percentage 30s
Mic / Camera Active mic/camera indicator Event
Input Source Keyboard input source Event
Time Clock (customizable format) 2s
Date Date & calendar events Event
Focus Timer Pomodoro-style timer Event
Chevron Section separator —

Plugins

StatusBar supports third-party plugins distributed as .statusplugin.zip archives via GitHub Releases. Install and manage plugins through the Preferences UI, the sbar plugins CLI, or by hand-editing plugins.yml.

Plugin Management UI

Manifest & lockfile

Plugin state lives in two YAML files alongside config.yml:

  • ~/.config/statusbar/plugins.yml — declarative manifest. Each entry is source: github:owner/repo plus version: "1.2.0" (exact) or version: latest. Safe to commit alongside your dotfiles.
  • ~/.config/statusbar/plugins-lock.yml — resolved snapshot. Auto-generated and updated by sync; records the exact tag, asset URL, and zip SHA-256 needed to reinstall the same versions on another machine.

GUI install/uninstall, manual edits, sbar plugins install/uninstall, and sbar plugins sync all update the same files. A "Sync Plugins" button in Preferences (and sbar plugins sync) reconciles the manifest with what's installed: missing plugins are downloaded, declared updates applied, and plugins removed from plugins.yml are auto-uninstalled.

Run sbar plugins sync --frozen to install strictly from the lockfile without contacting GitHub — useful for CI or restoring a known-good environment.

From the CLI:

sbar plugins install hytfjwr/statusbar-plugin-spotify       # latest release
sbar plugins install hytfjwr/statusbar-plugin-spotify --version 0.3.0
sbar plugins install https://github.com/hytfjwr/statusbar-plugin-spotify  # clone URLs work too
sbar plugins uninstall hytfjwr/statusbar-plugin-spotify

Install resolves --version against GitHub releases (omit it to pick latest, or to preserve a pin already in plugins.yml), downloads the .statusplugin.zip asset, updates the manifest + lockfile, and hot-loads the dylib so the widget appears without restart. Uninstall reverses all of that.

Official Plugins

Plugin Description Update
AeroSpace Tiling window manager workspace indicator Event
Spotify Now playing track title & artist Event
Docker Running container count 10s
VPN VPN connection status 5s
Claude Claude API usage & status Event

Install from Preferences > Plugins > Add Plugin using hytfjwr/<plugin-name>.

Create Your Own

Use the plugin template to get started.

Configuration

A default config is generated at ~/.config/statusbar/config.yml on first launch. The file is hot-reloaded — edits are applied instantly without restarting. All settings are also available through the Preferences window (Apple Menu > Preferences).

Plugin state is split across plugins.yml (declarative manifest) and plugins-lock.yml (resolved versions and checksums) in the same directory — see Plugins for details.

Bar
Key Type Default Description
height number 40 Bar height in pixels
cornerRadius number 12 Corner radius in pixels
margin number 8 Margin from screen edges
yOffset number 4 Vertical offset from top
widgetSpacing number 6 Space between widgets
widgetPaddingH number 6 Horizontal padding inside each widget
Appearance
Key Type Default Description
accent hex color #007AFF Accent color
green hex color #34C759 Green semantic color
yellow hex color #FF9F0A Yellow semantic color
red hex color #FF3B30 Red semantic color
cyan hex color #64D2FF Cyan semantic color
purple hex color #BF5AF2 Purple semantic color
barTint hex color #000000 Bar background tint color
barTintOpacity number 0.0 Bar tint opacity (0.0–1.0)
textPrimaryOpacity number 1.0 Primary text opacity
textSecondaryOpacity number 0.55 Secondary text opacity
textTertiaryOpacity number 0.30 Tertiary text opacity
shadowEnabled bool true Drop shadow on bar
popupCornerRadius number 10 Popup corner radius
popupPadding number 12 Popup internal padding
Typography
Key Type Default Description
iconFontSize number 13 Icon font size (pt)
labelFontSize number 13 Label font size (pt)
smallFontSize number 11 Small text font size (pt)
monoFontSize number 12 Monospace font size (pt)
Graphs
Key Type Default Description
width number 30 Graph width in pixels
height number 14 Graph height in pixels
dataPoints integer 50 Number of data points to display
cpuColor hex color #007AFF CPU graph color
memoryColor hex color #34C759 Memory graph color
Behavior
Key Type Default Description
autoHide bool true Auto-hide bar at top edge
autoHideDwellTime number 0.3 Seconds before hiding
autoHideFadeDuration number 0.2 Fade animation duration (s)
launchAtLogin bool false Launch at system startup
Notifications
Key Type Default Description
batteryLow bool false Enable low battery alert
batteryThreshold number 20.0 Battery level (%) to trigger
cpuHigh bool false Enable high CPU alert
cpuThreshold number 90.0 CPU usage (%) to trigger
cpuSustainedDuration number 5.0 Seconds above threshold before alert
memoryHigh bool false Enable high memory alert
memoryThreshold number 90.0 Memory usage (%) to trigger
memorySustainedDuration number 5.0 Seconds above threshold before alert
bluetoothBatteryLow bool false Enable low Bluetooth device battery alert (AirPods L/R, Magic Mouse, …)
bluetoothBatteryThreshold number 20.0 Device battery level (%) to trigger
Widget Layout

Each widget entry in the widgets array:

Key Type Description
id string Widget identifier (e.g. "time", "cpu", "battery")
section string Position: "left", "center", or "right"
sortIndex integer Order within the section
visible bool Whether the widget is displayed
Example config
bar:
  height: 40
  cornerRadius: 12
  margin: 8
  yOffset: 4

appearance:
  accent: "#007AFF"
  barTint: "#000000"
  barTintOpacity: 0.0
  shadowEnabled: true

behavior:
  autoHide: true
  autoHideDwellTime: 0.3
  launchAtLogin: false

widgets:
  - id: apple-menu
    section: left
    sortIndex: 0
    visible: true
  - id: front-app
    section: left
    sortIndex: 1
    visible: true
  - id: time
    section: right
    sortIndex: 0
    visible: true
  - id: battery
    section: right
    sortIndex: 1
    visible: true

Custom Script Widgets

Define your own status bar items with a shell command — no plugin required. Add entries under customWidgets: at the top level of config.yml:

customWidgets:
  - id: k8s-context
    position: right
    icon: "⎈"
    script: "kubectl config current-context"
    interval: 30
    clickScript: "open -a Lens"
    timeout: 5
Key Type Default Description
id string (required) Unique identifier. Letters, digits, -, and _ only
script string (required) Shell command run via bash -c
position string right left, center, or right
icon string — Emoji or short text shown before the label
sfSymbol string — SF Symbol name shown before the label (used if icon is not set)
interval number 30 Seconds between script runs. 0 disables the timer (runs once at start, and again on click)
clickScript string — Shell command run when the widget is clicked. If omitted, clicking re-runs script
timeout number 5 Script timeout in seconds (clamped to 1–30)

By default the widget displays the first line of the script's stdout as its label. For richer output, print a JSON object instead:

{"text": "3 pods", "icon": "⎈", "sfSymbol": "circle.fill", "color": "#FF3B30"}

Any stdout starting with { is parsed this way; all fields are optional and override the corresponding customWidgets entry values for that update. This lets a script change its icon, label, or color dynamically (e.g. turn red when a check fails).

Custom widgets are hot-reloaded along with the rest of config.yml — add, edit, or remove an entry and save the file to see the change immediately.

CLI (sbar)

StatusBar includes a command-line tool sbar for controlling the app from the terminal or scripts. It communicates with the running app via Unix domain socket.

When installed via Homebrew, sbar is automatically available on your PATH. For development builds, run make install-cli.

Commands

# List all widgets
sbar list
sbar list --json

# Get widget details
sbar get battery
sbar get cpu-graph --json

# Set widget settings
sbar set battery showPercentage=true
sbar set cpu-graph visible=false
sbar set time format="HH:mm:ss"

# Set global preferences
sbar set --global bar.height=44
sbar set --global appearance.accent=#FF0000
sbar set --global behavior.autoHide=false

# Send custom events to plugin widgets
sbar trigger com.example.myapp.deploy_finished
sbar trigger com.example.myapp.count --payload 42
sbar trigger com.example.myapp.deploy --payload '{"repo":"myapp","status":"ok"}'

# Subscribe to real-time events (NDJSON stream)
sbar subscribe front_app_switched volume_changed config_reloaded

# Wildcard: subscribe to all battery events
sbar subscribe 'battery_*'

# Subscribe to all events
sbar subscribe '*'

# Pipe events to jq for filtering
sbar subscribe front_app_switched | jq '.payload'

# Toast notifications
sbar toast --title "Deploy done" --message "v1.2.3 shipped" --level success
sbar toast --title "CPU Warning" --level warning --duration 10
sbar toast --title "Error" --level error --action-label "Open Logs" --action "open /var/log"

# Plugin manifest (reads/writes ~/.config/statusbar/plugins.yml)
sbar plugins list
sbar plugins list --json
sbar plugins sync
sbar plugins sync --frozen                # install strictly from plugins-lock.yml, no network resolution
sbar plugins install owner/repo           # accepts owner/repo, github:owner/repo, or https URL
sbar plugins install owner/repo --version 1.2.0
sbar plugins install owner/repo --json    # emit InstalledPluginDTO for scripts
sbar plugins uninstall owner/repo

# Relaunch the app
sbar reload

Use --json for machine-readable output (pipe to jq for filtering).

sbar get bluetooth --json includes live connected-device state alongside settings:

  • settings.state.deviceCount — number of connected devices
  • settings.state.devices — JSON-encoded array of {id, name, category, battery?, batteryLeft?, batteryRight?, batteryCase?}. Decode with jq -r '.settings["state.devices"] | fromjson'.
Event subscription

sbar subscribe keeps the connection open and streams events as newline-delimited JSON (NDJSON) to stdout. Supports wildcard patterns — a name ending in * matches any event with that prefix (e.g., battery_* matches battery_changed, battery_charging_changed, battery_low).

Raw events (emitted on every value change):

Event Payload Source
battery_changed percent, charging, hasBattery Battery widget
cpu_updated percent CPU graph widget
memory_updated percent Memory graph widget
network_updated downloadBytesPerSec, uploadBytesPerSec Network widget
disk_updated usedPercent, usedBytes, totalBytes Disk widget
volume_changed volume, muted Volume widget
mic_camera_changed micActive, cameraActive Mic/Camera widget

State transition events (emitted on discrete state changes):

Event Payload Trigger
front_app_switched appName, bundleID Active app changes
battery_charging_changed charging Charger plugged/unplugged
input_source_changed abbreviation Keyboard source switched
volume_muted — Audio muted
volume_unmuted — Audio unmuted
mic_activated / mic_deactivated — Microphone starts/stops
camera_activated / camera_deactivated — Camera starts/stops
bluetooth_devices_changed connectedCount, deviceNames, devices[] (name, category, optional battery/batteryLeft/batteryRight/batteryCase) Device list changes
bluetooth_device_connected name, category New device connected
bluetooth_device_disconnected name Device disconnected
focus_timer_started mode, durationSeconds Timer started
focus_timer_stopped — Timer cancelled
focus_timer_completed mode Timer finished
calendar_next_event_changed title, startDate, timeUntilStartSeconds Next event changes
bluetooth_battery_low deviceName, component ("left"/"right"/null), percent, threshold Connected device battery drops below threshold

Threshold events (emitted when crossing configured boundaries):

Event Payload Trigger
battery_low percent, threshold Battery drops below threshold
cpu_high usagePercent, threshold, sustainedSeconds CPU sustained above threshold
memory_high usagePercent, threshold, sustainedSeconds Memory sustained above threshold
disk_high usedPercent, threshold Disk crosses 80% or 90%

Infrastructure events:

Event Trigger
config_reloaded Config file hot-reloaded from disk

Each line is a JSON object:

{"event":"front_app_switched","timestamp":1711411234.56,"payload":{"appName":"Safari","bundleID":"com.apple.Safari"}}

Examples:

# React to focus timer completion
sbar subscribe focus_timer_completed | while read -r line; do
  osascript -e 'display notification "Break time!" with title "Focus Timer"'
done

# Log all battery events
sbar subscribe 'battery_*' | jq -c '{event, payload}'

# Monitor privacy indicators
sbar subscribe mic_activated mic_deactivated camera_activated camera_deactivated

The stream ends when the app quits or the connection is interrupted (Ctrl-C).

Trigger events

sbar trigger sends custom events to plugin widgets that have subscribed to them via subscribedEvents. Plugins receive events through the handleEvent(_:) callback.

sbar trigger <event> [--payload <value>]
Argument Required Description
<event> Yes Fully-qualified event name (e.g. com.example.myapp.deploy_finished)
--payload No Event payload — parsed as JSON if valid, otherwise treated as a plain string

Payload examples:

sbar trigger com.example.myapp.ping                              # no payload
sbar trigger com.example.myapp.count --payload 42                # number
sbar trigger com.example.myapp.status --payload "building"       # string
sbar trigger com.example.myapp.deploy --payload '{"status":"ok"}' # JSON object
Toast notifications

sbar toast displays a Liquid Glass notification panel below the bar. Toasts stack vertically (up to 4) and auto-dismiss after a configurable duration.

sbar toast --title <text> [options]
Option Default Description
--title (required) Toast title
--message — Body text
--icon (per level) SF Symbol name
--level info info, success, warning, error
--duration 5 Auto-dismiss seconds (0 = persistent)
--action-label — Action button label
--action — Shell command on action click

Returns the toast ID (UUID) on success.

Plugins can also post toasts via ToastService.shared.post(request).

Global key paths

The sbar set --global command uses dot-separated key paths matching the YAML config structure:

Category Key paths
Bar bar.height, bar.cornerRadius, bar.margin, bar.yOffset, bar.widgetSpacing, bar.widgetPaddingH
Appearance appearance.accent, appearance.barTint, appearance.barTintOpacity, appearance.shadowEnabled, appearance.popupCornerRadius, appearance.popupPadding
Typography typography.iconFontSize, typography.labelFontSize, typography.smallFontSize, typography.monoFontSize
Graphs graphs.width, graphs.height, graphs.dataPoints, graphs.cpuColor, graphs.memoryColor
Behavior behavior.autoHide, behavior.autoHideDwellTime, behavior.autoHideFadeDuration, behavior.launchAtLogin, behavior.hideInFullscreen
Notifications notifications.batteryLow, notifications.batteryThreshold, notifications.cpuHigh, notifications.cpuThreshold, notifications.memoryHigh, notifications.memoryThreshold, notifications.bluetoothBatteryLow, notifications.bluetoothBatteryThreshold

License

MIT

About

Custom macOS status bar in pure Swift.

Topics

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages