Skip to main content

Quick start

This guide walks you through the basics: installing HopTab, granting permissions, pinning your first apps, and using the switcher.
1

Install HopTab

Download the latest release and move it to /Applications. Clear the Gatekeeper quarantine flag:
xattr -d com.apple.quarantine /Applications/HopTab.app
See the installation guide for detailed instructions.
2

Grant Accessibility permission

Launch HopTab from /Applications. You’ll be prompted to grant Accessibility permission.Go to System Settings > Privacy & Security > Accessibility and enable HopTab.
Without this permission, HopTab cannot detect keyboard shortcuts and will not function.
3

Pin your first apps

Click the HopTab menu bar icon (arrow icon) and select Settings.In the Pinned Apps tab, you’ll see a list of all running apps. Click an app to pin it. Pinned apps show a checkmark.Start simple: pin 2-3 apps you’re actively using right now (e.g. your browser, terminal, and editor).
4

Try the switcher

Press Option+Tab to bring up the app switcher overlay. You’ll see your pinned apps with icons.
  • Press Tab again (while holding Option) to cycle forward
  • Press Shift+Tab to cycle backward
  • Release Option to activate the selected app
  • Press Escape to cancel
That’s it! You’re now hopping between your pinned apps.

Customize the shortcut

The default shortcut is Option+Tab, but you can change it in Settings > Shortcut.
Choose from three presets:
  • ⌥ Option + Tab (default)
  • ⌃ Control + Tab
  • ⌥ Option + `
Select a preset from the dropdown in Settings.
Here’s how shortcuts are configured in code (HopTab/Models/ShortcutConfig.swift:6-48):
enum ShortcutPreset: String, Codable, CaseIterable, Identifiable {
    case optionTab
    case controlTab
    case optionBacktick

    var displayName: String {
        switch self {
        case .optionTab: return "\u{2325} Option + Tab"
        case .controlTab: return "\u{2303} Control + Tab"
        case .optionBacktick: return "\u{2325} Option + `"
        }
    }

    var modifierFlag: CGEventFlags {
        switch self {
        case .optionTab, .optionBacktick: return .maskAlternate
        case .controlTab: return .maskControl
        }
    }

    var keyCode: Int64 {
        switch self {
        case .optionTab, .controlTab: return Int64(kVK_Tab)
        case .optionBacktick: return Int64(kVK_ANSI_Grave)
        }
    }
}

Understanding the overlay

When you press your switcher shortcut, HopTab displays a floating overlay panel with your pinned apps. The overlay is implemented as an NSPanel that floats above all other windows (HopTab/Views/OverlayPanel.swift). The panel uses:
  • .nonactivatingPanel style — doesn’t steal focus from other apps
  • .screenSaver window level — floats above everything
  • Vibrancy blur effect — native macOS translucency
Here’s the panel setup:
init(contentRect: NSRect) {
    super.init(
        contentRect: contentRect,
        styleMask: [.borderless, .nonactivatingPanel],
        backing: .buffered,
        defer: false
    )
    
    level = .screenSaver
    isOpaque = false
    backgroundColor = .clear
    hasShadow = true
    collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary]
}

How apps are activated

When you release the modifier key, HopTab activates the selected app using the Accessibility API. This is more aggressive than the standard NSRunningApplication.activate() and works reliably with stubborn apps like Simulator (HopTab/Services/AppSwitcherService.swift:15-31):
private static func activateRunning(_ app: NSRunningApplication) {
    // 1. Unhide first — hidden apps won't come to front otherwise
    if app.isHidden {
        app.unhide()
    }

    // 2. Use the older, more aggressive activate API
    //    The macOS 14+ parameterless activate() is weaker and doesn't
    //    always raise windows for apps like Simulator, Terminal, etc.
    app.activate(options: .activateIgnoringOtherApps)

    // 3. Raise the frontmost window via Accessibility API as a fallback.
    //    This forces the window to the top of the window stack, solving
    //    the issue where activate() updates the menu bar but leaves
    //    the window behind other apps.
    raiseWindows(of: app)
}
The raiseWindows function uses AXUIElementPerformAction with kAXRaiseAction to force windows to the front:
private static func raiseWindows(of app: NSRunningApplication) {
    let axApp = AXUIElementCreateApplication(app.processIdentifier)

    var value: CFTypeRef?
    let result = AXUIElementCopyAttributeValue(axApp, kAXWindowsAttribute as CFString, &value)
    guard result == .success, let windows = value as? [AXUIElement] else { return }

    for window in windows {
        AXUIElementPerformAction(window, kAXRaiseAction as CFString)
    }
}

Next steps

Keyboard shortcuts

Master all the shortcuts for app and profile switching

Profiles

Create workflow-specific app collections

Desktop assignment

Auto-switch profiles when you swipe between desktops

Setup guide

Explore all configuration options

Build docs developers (and LLMs) love