hs.ui

Create custom user interfaces, alerts, dialogs, and file pickers

The hs.ui module provides a set of tools for creating custom user interfaces in Hammerspoon with SwiftUI-like declarative syntax.

Key Features

  • Custom Windows: Build custom UI windows with shapes, text, and layouts
  • Alerts: Display temporary on-screen notifications
  • Dialogs: Show modal dialogs with custom buttons and callbacks
  • Text Input: Prompt users for text input
  • File Pickers: Let users select files or directories
  • Reactive Colors: Pass an HSColor object to .fill(), .stroke(), or .foregroundColor(), then call .set() on it from any callback to re-render the canvas automatically
  • Reactive Text: Create a string with hs.ui.string(), pass it to .text(), then call .set() on it to update the displayed content live
  • Reactive Images: Pass an HSImage object to .image(), then call .set() on it to swap the image without rebuilding the window

Basic Examples

Simple Alert

hs.ui.alert("Task completed!")
    .duration(3)
    .show();

Dialog with Buttons

hs.ui.dialog("Save changes?")
    .informativeText("Your document has unsaved changes.")
    .buttons(["Save", "Don't Save", "Cancel"])
    .onButton((index) => {
        if (index === 0) print("Saving...");
    })
    .show();

Text Input Prompt

hs.ui.textPrompt("Enter your name")
    .defaultText("John Doe")
    .onButton((buttonIndex, text) => {
        print("User entered: " + text);
    })
    .show();

File Picker

hs.ui.filePicker()
    .message("Choose a file")
    .allowedFileTypes(["txt", "md"])
    .onSelection((path) => {
        if (path) print("Selected: " + path);
    })
    .show();

Custom Window

hs.ui.window({x: 100, y: 100, w: 300, h: 200})
    .vstack()
        .spacing(10)
        .padding(20)
        .text("Hello, World!")
            .font(HSFont.title())
            .foregroundColor("#FFFFFF")
        .rectangle()
            .fill("#4A90E2")
            .cornerRadius(10)
            .frame({w: "100%", h: 60})
    .end()
    .backgroundColor("#2C3E50")
    .show();

Reactive Color on Hover

// Create a mutable color, then mutate it inside the hover callback
const btnColor = HSColor.hex("#4A90E2");

hs.ui.window({x: 100, y: 100, w: 160, h: 60})
    .rectangle()
        .fill(btnColor)
        .cornerRadius(8)
        .frame({w: "100%", h: "100%"})
        .onHover((isHovered) => {
            btnColor.set(isHovered ? "#E24A4A" : "#4A90E2");
        })
    .show();

Reactive Text on Hover

// Create a mutable string, then mutate it inside the hover callback
const label = hs.ui.string("Move your mouse here");

hs.ui.window({x: 100, y: 200, w: 220, h: 50})
    .text(label)
        .font(HSFont.body())
        .foregroundColor("#FFFFFF")
        .onHover((isHovered) => {
            label.set(isHovered ? "You're hovering!" : "Move your mouse here");
        })
    .show();

Reactive Image on Click

// Toggle between two system icons on each click
const icon = HSImage.fromName("NSStatusAvailable");

hs.ui.window({x: 100, y: 300, w: 80, h: 80})
    .image(icon)
        .resizable()
        .aspectRatio("fit")
        .frame({w: 64, h: 64})
        .onClick(() => {
            const next = (icon.name() === "NSStatusAvailable")
                ? HSImage.fromName("NSStatusUnavailable")
                : HSImage.fromName("NSStatusAvailable");
            icon.set(next);
        })
    .show();

Complete Example: Status Dashboard

Here's a more complex example showing how to build an interactive status dashboard that combines multiple UI elements:

// Create a status dashboard window
const statusWindow = hs.ui.window({x: 100, y: 100, w: 400, h: 500})
    .vstack()
        .spacing(15)
        .padding(20)

        // Header
        .text("System Status Dashboard")
            .font(HSFont.largeTitle())
            .foregroundColor("#FFFFFF")

        // Status cards
        .hstack()
            .spacing(10)
            .vstack()
                .spacing(5)
                .rectangle()
                    .fill("#4CAF50")
                    .cornerRadius(8)
                    .frame({w: 180, h: 100})
                .text("CPU: 45%")
                    .font(HSFont.headline())
                    .foregroundColor("#FFFFFF")
            .end()
            .vstack()
                .spacing(5)
                .rectangle()
                    .fill("#2196F3")
                    .cornerRadius(8)
                    .frame({w: 180, h: 100})
                .text("Memory: 8.2GB")
                    .font(HSFont.headline())
                    .foregroundColor("#FFFFFF")
            .end()
        .end()

        // Activity indicator with image
        .hstack()
            .spacing(10)
            .image(HSImage.fromName("NSComputer"))
                .resizable()
                .aspectRatio("fit")
                .frame({w: 64, h: 64})
            .vstack()
                .text("System Running")
                    .font(HSFont.title())
                .text("All services operational")
                    .font(HSFont.caption())
                    .foregroundColor("#A0A0A0")
            .end()
        .end()

        // Circle status indicators
        .hstack()
            .spacing(20)
            .circle()
                .fill("#4CAF50")
                .frame({w: 30, h: 30})
            .circle()
                .fill("#FFC107")
                .frame({w: 30, h: 30})
            .circle()
                .fill("#F44336")
                .frame({w: 30, h: 30})
        .end()
    .end()
    .backgroundColor("#2C3E50");

// Show the dashboard
statusWindow.show();

// Later, interact with dialogs
hs.ui.dialog("Shutdown system?")
    .informativeText("This will close all applications.")
    .buttons(["Shutdown", "Cancel"])
    .onButton((index) => {
        if (index === 0) {
            hs.ui.alert("Shutting down...")
                .duration(3)
                .show();
        }
    })
    .show();

Complete Example: Reactive Hover Card

Demonstrates reactive colors and reactive text together — a single .onHover() callback updates both the fill color of a shape and the content of a text label:

const cardColor = HSColor.hex("#3498DB");
const cardLabel = hs.ui.string("Hover the card");

hs.ui.window({x: 100, y: 100, w: 220, h: 120})
    .vstack()
        .spacing(12)
        .padding(16)
        .rectangle()
            .fill(cardColor)
            .cornerRadius(10)
            .frame({w: "100%", h: 60})
            .onHover((isHovered) => {
                cardColor.set(isHovered ? "#E74C3C" : "#3498DB");
                cardLabel.set(isHovered ? "You found it!" : "Hover the card");
            })
        .text(cardLabel)
            .font(HSFont.headline())
            .foregroundColor("#FFFFFF")
    .end()
    .backgroundColor("#1A252F")
    .show();

Types

This module provides the following types:

Properties

This module has no properties.

Methods

hs.ui.window

Create a custom UI window Creates a borderless window that can contain custom UI elements built using a declarative, SwiftUI-like syntax with shapes, text, and layout containers.


Declaration

hs.ui.window(dict) -> HSUIWindow

Parameters

  • dict {[key: string]: any}

    Dictionary with keys: `x`, `y`, `w`, `h` (all numbers)

Return Value

An `HSUIWindow` object for chaining

hs.ui.alert

Create a temporary on-screen alert Displays a temporary notification that automatically dismisses after the specified duration. Similar to the old `hs.alert` module but with more features.


Declaration

hs.ui.alert(message) -> HSUIAlert

Parameters

  • message string

    The message text to display

Return Value

An `HSUIAlert` object for chaining

hs.ui.dialog

Create a modal dialog with buttons Shows a blocking dialog with customizable message, informative text, and buttons. Use the callback to handle button presses.


Declaration

hs.ui.dialog(message) -> HSUIDialog

Parameters

  • message string

    The main message text

Return Value

An `HSUIDialog` object for chaining

hs.ui.textPrompt

Create a text input prompt Shows a modal dialog with a text input field. The callback receives the button index and the entered text.


Declaration

hs.ui.textPrompt(message) -> HSUITextPrompt

Parameters

  • message string

    The prompt message

Return Value

An `HSUITextPrompt` object for chaining

hs.ui.string

Create a reactive string for binding text element content to a dynamic value An `HSString` is a reactive value container. When passed to `.text()`, the canvas automatically re-renders whenever `.set()` is called from JavaScript.


Declaration

hs.ui.string(initialValue) -> HSString

Parameters

  • initialValue string

    The starting string value

Return Value

An `HSString` object whose value can be updated with `.set()`

hs.ui.filePicker

Create a file or directory picker Shows a standard macOS file picker dialog. Can be configured to select files, directories, or both, with support for file type filtering and multiple selection.


Declaration

hs.ui.filePicker() -> HSUIFilePicker

Parameters

None

Return Value

An `HSUIFilePicker` object for chaining