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:
| Feature | macOS | iOS | Linux | Windows | Web |
|---|---|---|---|---|---|
| Buttons | NSButton | UIButton | GtkButton | HWND Button | <button> |
| Text | NSTextField | UILabel | GtkLabel | Static HWND | <span> |
| Layout | NSStackView | UIStackView | GtkBox | Manual layout | Flexbox |
| Menus | NSMenu | — | GMenu | HMENU | DOM |
Platform-specific behavior is noted on each widget’s documentation page.
Next Steps
- Widgets — All available widgets
- Layout — Arranging widgets
- State Management — Reactive state and bindings
- Styling — Colors, fonts, sizing
- Events — Click, hover, keyboard