Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

UI Overview

Perry’s perry/ui module lets you build native desktop and mobile apps with declarative TypeScript. Your UI code compiles directly to platform-native widgets — AppKit on macOS, UIKit on iOS, GTK4 on Linux, Win32 on Windows, and DOM elements on the web.

Quick Start

// demonstrates: the smallest complete Perry UI app
// docs: docs/src/ui/overview.md
// platforms: macos, linux, windows
// targets: ios-simulator, web, wasm

import { App, Text, VStack } from "perry/ui"

App({
    title: "My App",
    width: 400,
    height: 300,
    body: VStack(16, [
        Text("Hello from Perry!"),
    ]),
})
perry app.ts -o app && ./app

Mental Model

Perry’s UI follows the same model as SwiftUI and Flutter: you compose native widgets using stack-based layout containers (VStack, HStack, ZStack), control alignment and distribution, and style widgets directly via method calls. If you’re coming from web development, the key shift is:

  • Layout is controlled by stack alignment, distribution, and spacers — not CSS properties. See Layout.
  • Styling is applied directly to widgets — not through stylesheets. See Styling.
  • Absolute positioning uses overlays (widgetAddOverlay + widgetSetOverlayFrame) — not position: absolute/relative.
  • Design tokens come from the perry-styling package. See Theming.

App Lifecycle

Every Perry UI app starts with App():

import { App, VStack, Text } from "perry/ui";

App({
  title: "Window Title",
  width: 800,
  height: 600,
  body: VStack(16, [
    Text("Content here"),
  ]),
});

App({}) accepts a config object with the following properties:

PropertyTypeDescription
titlestringWindow title
widthnumberInitial window width
heightnumberInitial window height
bodywidgetRoot widget
iconstringApp icon file path (optional)
framelessbooleanRemove title bar (optional)
levelstringWindow z-order: "floating", "statusBar", "modal" (optional)
transparentbooleanTransparent background (optional)
vibrancystringNative blur material, e.g. "sidebar" (optional)
activationPolicystring"regular", "accessory" (no dock icon), "background" (optional)

See Multi-Window for full documentation on window properties.

Lifecycle Hooks

import { App, onActivate, onTerminate } from "perry/ui";

onActivate(() => {
  console.log("App became active");
});

onTerminate(() => {
  console.log("App is closing");
});

App({ title: "My App", width: 800, height: 600, body: /* ... */ });

Widget Tree

Perry UIs are built as a tree of widgets:

import { App, Text, Button, VStack, HStack } from "perry/ui";

App({
  title: "Layout Demo",
  width: 400,
  height: 300,
  body: VStack(16, [
    Text("Header"),
    HStack(8, [
      Button("Left", () => console.log("left")),
      Button("Right", () => console.log("right")),
    ]),
    Text("Footer"),
  ]),
});

Widgets are created by calling their constructor functions. Layout containers (VStack, HStack, ZStack) accept a spacing value (in points) followed by an array of child widgets.

Handle-Based Architecture

Under the hood, each widget is a handle — a small integer that references a native platform object. When you call Text("hello"), Perry creates a native NSTextField (macOS), UILabel (iOS), GtkLabel (Linux), or <span> (web) and returns a handle you can use to modify it.

const label = Text("Hello");
label.setFontSize(18);        // Modifies the native widget
label.setColor("#FF0000");     // Through the handle

Imports

All UI functions are imported from perry/ui:

import {
  // App lifecycle
  App, onActivate, onTerminate,

  // Widgets
  Text, Button, TextField, SecureField, Toggle, Slider,
  Image, ProgressView, Picker,

  // Layout
  VStack, HStack, ZStack, ScrollView, Spacer, Divider,
  NavigationStack, LazyVStack, Form, Section,
  VStackWithInsets, HStackWithInsets, SplitView, splitViewAddChild,

  // Layout control
  stackSetAlignment, stackSetDistribution, stackSetDetachesHidden,
  widgetMatchParentWidth, widgetMatchParentHeight, widgetSetHugging,
  widgetAddOverlay, widgetSetOverlayFrame,

  // State
  State, ForEach,

  // Dialogs
  openFileDialog, saveFileDialog, alert, Sheet,

  // Menus
  menuBarCreate, menuBarAddMenu, contextMenu,

  // Canvas
  Canvas,

  // Table
  Table,

  // Window
  Window,

  // Camera (iOS)
  CameraView, cameraStart, cameraStop, cameraFreeze, cameraUnfreeze,
  cameraSampleColor, cameraSetOnTap,
} from "perry/ui";

Platform Differences

The same code runs on all platforms, but the look and feel matches each platform’s native style:

FeaturemacOSiOSLinuxWindowsWeb
ButtonsNSButtonUIButtonGtkButtonHWND Button<button>
TextNSTextFieldUILabelGtkLabelStatic HWND<span>
LayoutNSStackViewUIStackViewGtkBoxManual layoutFlexbox
MenusNSMenuGMenuHMENUDOM

Platform-specific behavior is noted on each widget’s documentation page.

Next Steps