# How FluidVoice Displays Its Status in the Menu Bar

> Learn how FluidVoice displays its status in the menu bar using NSStatusItem and MenuBarManager to show real-time dictation updates and pipeline state changes.

- Repository: [ALTIC/FluidVoice](https://github.com/altic-dev/FluidVoice)
- Tags: how-to-guide
- Published: 2026-08-14

---

**FluidVoice uses a native `NSStatusItem` managed by the `MenuBarManager` service to display real-time dictation status, updating a dedicated `statusMenuItem` title and optional button icons whenever the speech pipeline changes state.**

FluidVoice is an open-source macOS dictation application that provides immediate visual feedback in the system menu bar. The app implements a custom `MenuBarManager` class that bridges the automatic speech recognition (ASR) pipeline with AppKit's `NSStatusBar` API, ensuring users always know whether the app is idle, listening, or processing audio.

## The MenuBarManager Service

### Creating the NSStatusItem

In [`Sources/Fluid/Services/MenuBarManager.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Services/MenuBarManager.swift), the manager instantiates a system status item during initialization. The implementation uses `NSStatusItem.squareLength` to allocate space for the icon in the menu bar.

```swift
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.squareLength)
statusItem?.menu = self.menu

```

### The statusMenuItem Reference

The manager maintains a dedicated `NSMenuItem` instance called `statusMenuItem` (referenced around line 494) that serves as the primary text display for current state information. This item is inserted at the top of the dropdown menu and updated dynamically as the dictation pipeline changes state.

```swift
if let statusMenuItem = statusMenuItem {
    menu.addItem(statusMenuItem)
}

```

## Status Update Flow

### Initialization in fluidApp.swift

The application entry point in [`Sources/Fluid/fluidApp.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/fluidApp.swift) creates the manager as a state object and injects it into the environment, making it accessible throughout the view hierarchy.

```swift
@StateObject private var menuBarManager = MenuBarManager()

var body: some Scene {
    WindowGroup {
        ContentView()
            .environmentObject(self.menuBarManager)
    }
}

```

### Observing ASR State Changes

[`Sources/Fluid/ContentView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/ContentView.swift) observes the ASR service and propagates status changes to the menu bar manager. When the speech recognizer transitions between authorized, listening, processing, and idle states, the view calls `menuBarManager.setStatus(_:)` to refresh the display.

```swift
// Propagating state change to menu bar
menuBarManager.setStatus(currentStatusString)

```

### Mapping Status Strings in NotchContentViews.swift

Human-readable status labels like "Listening", "Processing…", and "Idle" are defined in [`Sources/Fluid/Views/NotchContentViews.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Views/NotchContentViews.swift). These strings are reused by `MenuBarManager` to maintain consistency between the notch UI and the menu bar display.

## Implementation Details

When the dictation pipeline transitions between states, `MenuBarManager.setStatus(_:)` updates both the tooltip and the visible menu item text.

```swift
func setStatus(_ status: String) {
    statusMenuItem?.title = status
    statusItem?.button?.toolTip = status
    
    // Optional visual feedback via icon
    if status == "Listening" {
        statusItem?.button?.image = NSImage(systemSymbolName: "mic.fill", accessibilityDescription: nil)
    } else {
        statusItem?.button?.image = NSImage(systemSymbolName: "mic", accessibilityDescription: nil)
    }
}

```

The status item button may also display dynamic images to provide additional visual cues—for example, showing a filled microphone icon when actively listening—by modifying `statusItem.button?.image` alongside the text updates.

## Summary

- **MenuBarManager.swift** creates and owns the `NSStatusItem`, configuring it with `NSStatusItem.squareLength` and attaching a dropdown menu.
- **statusMenuItem** is a dedicated `NSMenuItem` that displays the current dictation state as text at the top of the menu.
- **fluidApp.swift** initializes the manager as a `@StateObject` and injects it into the SwiftUI environment for global access.
- **ContentView.swift** bridges the ASR service to the menu bar by calling `setStatus(_:)` whenever the speech pipeline state changes.
- **NotchContentViews.swift** provides the canonical status strings used for both the notch overlay and menu bar display, ensuring UI consistency.

## Frequently Asked Questions

### What NSStatusItem length does FluidVoice use?

FluidVoice initializes the status item using `NSStatusItem.squareLength` in [`MenuBarManager.swift`](https://github.com/altic-dev/FluidVoice/blob/main/MenuBarManager.swift), allocating sufficient space for the icon while maintaining a compact footprint in the macOS menu bar.

### How does the status text update when dictation starts?

When the ASR service detects a state change, [`ContentView.swift`](https://github.com/altic-dev/FluidVoice/blob/main/ContentView.swift) calls `menuBarManager.setStatus(_:)` with the new state string. This method immediately updates `statusMenuItem.title` and the button's `toolTip` property to reflect the current activity.

### Where are the status strings defined?

The human-readable labels for states like "Listening" and "Processing…" are defined in [`Sources/Fluid/Views/NotchContentViews.swift`](https://github.com/altic-dev/FluidVoice/blob/main/Sources/Fluid/Views/NotchContentViews.swift) and reused by `MenuBarManager` to ensure the menu bar text matches the transient overlay shown in the notch area.

### Can the menu bar icon change based on status?

Yes. While the primary status display uses `statusMenuItem.title`, the implementation can also modify `statusItem.button?.image` to show different system symbols—such as switching between `mic` and `mic.fill`—based on whether the app is actively recording or idle.