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

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

App("My App", () =>
  VStack([
    Text("Hello from Perry!"),
  ])
);
perry app.ts -o app && ./app

App Lifecycle

Every Perry UI app starts with App():

import { App } from "perry/ui";

App("Window Title", () => {
  // Build and return your widget tree
  return VStack([
    Text("Content here"),
  ]);
});

App(title, renderFn) creates the native application, opens a window, and renders your widget tree. The render function is called initially and re-invoked when reactive state changes.

Lifecycle Hooks

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

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

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

App("My App", () => { /* ... */ });

Widget Tree

Perry UIs are built as a tree of widgets:

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

App("Layout Demo", () =>
  VStack([
    Text("Header"),
    HStack([
      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 arrays 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,

  // State
  State, ForEach,

  // Dialogs
  openFileDialog, saveFileDialog, alert, Sheet,

  // Menus
  menuBarCreate, menuBarAddMenu, contextMenu,

  // Canvas
  Canvas,

  // Table
  Table,

  // Window
  Window,
} 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